diff --git a/.github/workflows/rake.yml b/.github/workflows/rake.yml index b13a247..c82bf5e 100644 --- a/.github/workflows/rake.yml +++ b/.github/workflows/rake.yml @@ -12,4 +12,4 @@ jobs: rake: uses: metanorma/ci/.github/workflows/generic-rake.yml@main secrets: - pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }} + pat_token: ${{ secrets.RIBOSE_CI_PAT_TOKEN }} diff --git a/.gitignore b/.gitignore index 5f4a30e..3a47b21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -_site/* +_site*/* *.scssc Gemfile.lock -jekyll-theme-rop-*.gem \ No newline at end of file +prexian-*.gem +_hub-site +coverage +pkg/ \ No newline at end of file diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..8bcb8ae --- /dev/null +++ b/.rspec @@ -0,0 +1,4 @@ +--require spec_helper +--exclude-pattern "spec/fixtures/**/*_spec.rb" +--format documentation +--color diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ec565fa..213927d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -11,14 +11,14 @@ # Include: **/*.gemspec Gemspec/RequiredRubyVersion: Exclude: - - 'jekyll-theme-rop.gemspec' + - 'prexian.gemspec' # Offense count: 2 # Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: - - 'lib/rop/filterable_index.rb' - - 'lib/rop/png_diagram_page.rb' + - 'lib/prexian/filterable_index.rb' + - 'lib/prexian/png_diagram_page.rb' # Offense count: 11 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. @@ -69,7 +69,7 @@ Naming/FileName: Exclude: - 'Rakefile.rb' - 'lib/jekyll-theme-open-project.rb' - - 'lib/jekyll-theme-rop.rb' + - 'lib/prexian.rb' # Offense count: 1 # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs. @@ -80,12 +80,12 @@ Naming/FileName: Naming/PredicateName: Exclude: - 'spec/**/*' - - 'lib/rop/project_reader.rb' + - 'lib/prexian/project_reader.rb' # Offense count: 1 Style/ClassVars: Exclude: - - 'lib/rop/project_reader.rb' + - 'lib/prexian/project_reader.rb' # Offense count: 9 # Configuration parameters: AllowedConstants. @@ -93,11 +93,11 @@ Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - - 'lib/jekyll-theme-rop.rb' - - 'lib/rop/filterable_index.rb' - - 'lib/rop/png_diagram_page.rb' - - 'lib/rop/project_reader.rb' - - 'lib/rop/spec_builder.rb' + - 'lib/prexian.rb' + - 'lib/prexian/filterable_index.rb' + - 'lib/prexian/png_diagram_page.rb' + - 'lib/prexian/project_reader.rb' + - 'lib/prexian/spec_builder.rb' # Offense count: 5 # Configuration parameters: AllowedVariables. diff --git a/Gemfile b/Gemfile index 8fc1bbb..383b5f2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,8 @@ # frozen_string_literal: true source 'https://rubygems.org' + +# Specify your gem's dependencies in prexian.gemspec gemspec -gem 'bundler' -gem 'rake' -gem 'rubocop' +gem 'rake', '~> 13.0' diff --git a/README.md b/README.md index 0df46d3..73758b7 100644 --- a/README.md +++ b/README.md @@ -1,1082 +1,1247 @@ -# The ROP Jekyll theme (Ribose Open Project theme) +# Prexian -ROP is a Jekyll theme (with accompanying plugin code) aiming to help -organizations and individuals present open-source software and specifications in -a navigable and elegant way. +*A Jekyll theme for open-source software and specification projects* -The gem is released as `jekyll-theme-rop`. +[![Gem Version](https://badge.fury.io/rb/prexian.svg)](https://badge.fury.io/rb/prexian) +[![Build Status](https://github.com/riboseinc/prexian/workflows/test/badge.svg)](https://github.com/riboseinc/prexian/actions) -ROP fits two types of sites: +Prexian is a powerful Jekyll theme designed to help organizations and individuals present open-source software and specifications in a navigable and elegant way. It supports both individual project sites and hub sites that aggregate multiple projects. -* a site that describes one individual project; -* a site that combine multiple project sites into an open hub site. +## ✨ Features -**Demo**: See [Ribose](https://www.ribose.com/) project sites -- for example, -[Metanorma](https://www.metanorma.com), -[RNP](https://www.rnpgp.com), -[Cryptode](https://www.cryptode.com), -[Relaton](https://www.relaton.com). +- **Two Site Types**: Create individual project sites or hub sites that aggregate multiple projects +- **Smart Content Management**: Automatically fetch and display software documentation, specifications, and project metadata from Git repositories +- **Performance Optimized**: Intelligent caching system with shallow clones and sparse checkout +- **Modern Design**: Clean, responsive design with customizable styling +- **SEO Ready**: Built-in SEO optimization with jekyll-seo-tag integration +- **Blog Support**: Full-featured blogging with author profiles and social links +- **Search Integration**: Optional Algolia search support +- **AsciiDoc Support**: First-class support for AsciiDoc content authoring -See also: CI_OPS for how to set up automated build and deployment of sites -to AWS S3. +## 🚀 Quick Start -NOTE: This theme was previously named `jekyll-theme-open-project` with a helper -gem `jekyll-theme-open-project-helpers`. +### Installation +Add this line to your Jekyll site's `Gemfile`: -## Migrating from `jekyll-theme-open-project` to the new `jekyll-theme-rop` - -Follow these steps: - -1. Update your Open Project Gemfile to remove all previously used dependencies -from `jekyll-theme-open-project` (including `git`), and replace it like this: ```ruby -# If you have any plugins, put them here! -group :jekyll_plugins do - gem "jekyll-theme-rop" -end +gem "prexian" ``` -2. Update your `_config.yml` file to remove all previously used plugins from `jekyll-theme-open-project` and only use `jekyll-theme-rop`, so it becomes: -```yaml -plugins: - - jekyll-theme-rop -``` - -3. Replace in SCSS files all mention of import files with their renamed counterparts: - * `@import 'jekyll-theme-open-project'` => ``@import 'jekyll-theme-rop'` - * `'open-project-mixins'` => remove because it was already included. +And then execute: -4. If you use the `png_diagrams` feature in any page layout, replace as follows: -```diff --engine: png_diagrams -+engine: png_diagram_page +```bash +$ bundle install ``` +Or install it yourself as: -## Contents - -* Creating a site: [how to](#starting-a-site-with-this-theme) - - * [General site setup](#general-setup) - * [Hub site setup](#hub-site) - * [Project site setup](#project-site) and describing your software and specs +```bash +$ gem install prexian +``` -* Customizing site looks: +### Basic Setup - * [Style customization](#style-customization) - * [SVG guidelines](#svg-guidelines) - * [Content guidelines](#content-guidelines) +1. **Create a new Jekyll site**: + ```bash + jekyll new my-project-site + cd my-project-site + ``` -* [Authoring content](#authoring-content) +2. **Add Prexian to your Gemfile**: + ```ruby + gem "prexian" + ``` -* References: +3. **Configure your site** in `_config.yml`: + ```yaml + theme: prexian - * [Layouts](#theme-layouts) - * [Includes](#theme-includes) + plugins: + - prexian + prexian: + title: My Project + description: An awesome open-source project + tagline: Making the world a better place + site_type: project # or 'hub' + ``` -## Getting started +4. **Build and serve**: + ```bash + bundle exec jekyll serve + ``` -### Set up Ruby and Jekyll +## 📋 Minimum Requirements -The currently recommended Ruby version is 3.3. +### Required Configuration -NOTE: In case you don't use Ruby often, the easiest way to install one may be -with RVM. +Every Prexian site must have these minimum configuration settings in `_config.yml`: -The currently recommended Jekyll version is 3 or newer -(read about [Jekyll installation](https://jekyllrb.com/docs/#instructions)). +```yaml +# Basic Jekyll configuration +title: Your Site Title +description: Your site description -NOTE: This theme is known to not work with Ruby older than 2.3. -It has not been tested on newer versions. +# Theme configuration +theme: prexian +plugins: + - prexian -### Start a new Jekyll site +# Prexian configuration (required) +prexian: + site_type: project # or 'hub' - REQUIRED + title: Your Project Title + description: Your project description -```sh -jekyll new my-open-site +# Collections (required for content types you want to use) +collections: + software: + output: true + permalink: /software/:path/ + specs: + output: true + permalink: /specs/:path/ + posts: + output: true + permalink: /blog/:year-:month-:day-:title/ + # For hub sites only: + projects: + output: false + +# Defaults (required) +defaults: + - scope: + path: "" + values: + layout: default + - scope: + path: _posts + type: posts + values: + layout: post + - scope: + path: _software + type: software + values: + layout: product + - scope: + path: _specs + type: specs + values: + layout: spec ``` -If you use Git for site source version management, -see the “Extra .gitignore rules” section below -for additional lines you should add to your `.gitignore`. +### Required Files -### Install Open Site theme into the Jekyll site +#### For All Sites -Add this line to your Jekyll site's `Gemfile`, -replacing default theme requirement: +- **`_config.yml`** - Site configuration (see above) +- **`index.md`** - Homepage content: + ```yaml + --- + layout: home + --- -```ruby -gem "jekyll-theme-rop" -``` + Your homepage content here... + ``` -(Jekyll’s default theme was “minima” at the time of this writing.) +#### For Project Sites -Also in the `Gemfile`, add two important plugins to the `:jekyll_plugins` group. -(The SEO tag plugin is not mandatory, but these docs assume you use it.) +If your project site has a parent hub, add this to `_config.yml`: -```ruby -group :jekyll_plugins do - gem "jekyll-theme-rop" - - # The following gems are automatically included by jekyll-theme-rop - # gem "jekyll-seo-tag" - # gem "jekyll-sitemap" - # gem "jekyll-data" - # gem "jekyll-asciidoc" - # gem "jekyll-external-links" - - # ...other plugins, if you use any -end +```yaml +prexian: + hub: + git_repo_url: https://github.com/your-org/hub-site + git_repo_branch: main # optional, defaults to 'main' + home_url: https://your-hub-site.com/ ``` -Execute the following to install dependencies: - - $ bundle - -### Configure your Open Site for the first time - -Edit `_config.yml` to add necessary site-wide configuration options, -and add files and folders to site contents. This step depends -on the type of site you’re creating: hub or individual project site. - -Further sections explain core concepts of open project and hub, and go -into detail about how to configure a project or hub site. - -Before building the first time you must do this: - -1. Configure [common settings](#common-settings) -2. Add your logo(s) according to [logo](#logo) - -Please see the [configuration section](#configuration) for more details. - -NOTE: It may be required to copy the following properties from -this theme’s `_config.yaml` to your site’s: `collections`, `includes_dir`. - -This is likely caused by changed behavior of jekyll-data gem in recent versions, -which is responsible for “inheritance” of `_config.yaml` between theme and site. +#### For Hub Sites -You can add any custom collections for your site -after collections copied from theme’s config. +- **`assets/img/symbol.svg`** - Your site's logo (SVG format recommended) inserted at the footer +- **`title.html`** - Your site's title (HTML format recommended) to be included in the footer +Without these two files the rendering of a project site will crash. -### Building site +TODO: Fix this problem by detecting and skipping. -Execute to build the site locally and watch for changes: - $ bundle exec jekyll serve --host mysite.local --port 4000 +- a **`_projects`** directory with project definitions: -This assumes you have mysite.local mapped in your hosts file, -otherwise omit --host and it’ll use “localhost” as domain name. +```yaml +# _projects/my-project.md +--- +title: My Project +description: Brief project description +site: + git_repo_url: https://github.com/your-org/my-project-site + git_repo_branch: main +home_url: https://your-org.github.io/my-project/ +--- +Detailed project description... +``` -## Configuration +### Optional Files -There are 3 areas to configure when you first create an Open Site, namely: +#### Site Favicon -* [Common setup](#common-setup), settings that apply to both Hub and Project sites; -* [Hub site](#hub-site); -* [Project site](#project-site) +- **`assets/favicon.png`** - Site favicon +#### Custom Styling -## Common setup +- **`assets/css/style.scss`** - Custom styles: + ```scss + --- + --- -### Git repository branch behavior + ... + @import "prexian"; + @import "prexian/custom-styles"; -You’ll see many instances of document frontmatter -referencing Git repository URLs. + // Your custom styles here + ``` -Note that, wherever a `[*_]repo_url` property is encountered, -a sibling property `[*_]repo_branch` is supported. -(This is new in 2.1.17, until that version branch “master” was used for all repositories.) +#### Content Pages -If you reference repositories that don’t use branch name “main”, -you must either: +- **`_pages/`** - Static pages (about, contact, etc.) +- **`_posts/`** - Blog posts +- **`_software/`** - Software components +- **`_specs/`** - Specifications -- use a sibling `[*_]repo_branch` property to specify your custom branch name - (you can search for `git_repo_branch`, `repo_branch`, `github_repo_branch` - in this document for examples), or +### Directory Structure Example -- specify `default_repo_branch` property in `config.yml` +``` +my-project-site/ +├── _config.yml # Required: Site configuration +├── index.md # Required: Homepage +├── Gemfile # Required: Dependencies +├── title.html # Required (for hub): Title file +├── assets/ +│ ├── symbol.svg # Required (for hub): Site logo +│ └── css/ +│ └── style.scss # Optional: Custom styles +├── _pages/ # Optional: Static pages +│ ├── about.md +│ └── contact.md +├── _posts/ # Optional: Blog posts +│ └── 2024-01-01-welcome.md +├── _software/ # Optional: Software components +│ └── my-tool.md +└── _specs/ # Optional: Specifications + └── my-spec.md +``` - (in this case, in scenarios with project sites being used in conjunction - with a hub site, `default_repo_branch` must be the same - across all project sites’ and their hub site’s `config.yml`—otherwise you’re advised - to use the previous option to avoid site build failure). +This is the absolute minimum needed to get a Prexian site running. You can then add content and customize as needed. -Note that, when a referenced Git repository doesn’t contain the necessary branch -(either explicitly specified custom branch, or `default_repo_branch`, or branch called “main”), -this will cause build failure of that project site, or a hub site using that project site. +## 📖 Site Types -### Common settings +Prexian supports two distinct types of sites, each with specific purposes and capabilities: -(mandatory) +### Project Site -These settings apply to both site types (hub and project). +A **project site** describes one individual project and can contain: -- You may want to remove the default `about.md` page added by Jekyll, - as this theme does not account for its existence. +- **Software components**: Individual software products with their documentation +- **Specifications**: Technical specifications and standards +- **Blog posts**: Project-specific announcements and articles +- **Documentation**: Project-wide documentation and guides +- **Pages**: Static pages for additional content -- Add `hero_include: home-hero.html` to YAML frontmatter - in your main `index.md`. +Project sites are designed to be comprehensive resources for a single project, providing everything users need to understand, use, and contribute to the project. -- Add following items to site’s `_config.yml` - (and don’t forget to remove default theme requirement there): +```yaml +# _config.yml for project site +prexian: + site_type: project + hub: + git_repo_url: https://github.com/your-org/hub-site + git_repo_branch: main + home_url: https://your-org.github.io/ +``` - ```yaml - url: https://example.com - # Site’s URL with protocol, without optional www. prefix - # and without trailing slash. - # Used e.g. for marking external links in docs and blog posts. - - github_repo_url: https://github.com/example-org/example.com - # URL to GitHub repo for the site. - # Using GitHub & specifying this setting is currently required - # for “suggest edits” buttons to show on documentation pages. - github_repo_branch: main - # Optional, default is `main`. +### Hub Site - title: Example - description: The example of examples - # The above two are used by jekyll-seo-tag for things such as - # `` and `<meta>` tags, as well as elsewhere by the theme. +A **hub site** aggregates multiple project sites into a unified portal. Hub sites can contain: - default_repo_branch: main - # Optional, default is `main`. - # Whenever branch name isn’t specified for some repository - # (such as project docs or specs), this name will be used - # during site’s build. - # (See branch behavior section for details.) +- **Projects collection**: Links to individual project sites +- **Aggregated software**: Software from all projects in the hub +- **Aggregated specifications**: Specifications from all projects +- **Hub-wide blog**: Organization-level announcements and articles +- **All project site components**: Hub sites can also have their own software, specs, and content - tagline: Because examples are very important - # Used in hero unit on main page. +Hub sites serve as the central entry point for organizations with multiple related projects, providing a unified view of all activities and resources. - social: - links: - - https://twitter.com/<orgname> - - https://github.com/<orgname> +```yaml +# _config.yml for hub site +prexian: + site_type: hub - legal: - name: Full Organization Name - tos_link: https://www.example.com/tos - privacy_policy_link: https://www.example.com/privacy - - # no_auto_fontawesome: yes - # Specify this only if you want to disable free Font Awesome CDN. - # IMPORTANT: In this case your site MUST specify include head.html with appropriate scripts. - # Theme design relies on Font Awesome “solid” and “brands” icon styles - # and expects them to be included in SVG mode. - # Without this setting, one-file FA distribution, all.js, is included from free FA CDN. - - theme: jekyll-theme-rop - - permalink: /blog/:month-:day-:year-:title/ - # It’s important that dash-separated permalink is used for blog posts. - # There’re no daily or monthly blog archive pages generated. - # Hub sites reference posts using that method, and it’s currently non-customizable. - # With `collections` configuration, specify permalink for posts - # correctly as well (for an example, see https://github.com/metanorma/metanorma.org/blob/d2b15f6d8c4cea73d45ad899374845ec38348ff1/_config.yml#L60). - ``` +collections: + projects: + output: false +``` -### Logo +## 🏗️ Building Project and Hub Sites -(mandatory) +### Adding Projects (Hub Sites Only) -By “logo” is meant the combination of site symbol as a graphic -and name as word(s). +Projects are defined in the `_projects` collection. Each project points to a separate Prexian project site: -- **Symbol** is basically an icon for the site. - Should look OK in dimensions of 30x30px, and fit inside a square. - Should be in SVG format (see also the SVG guidelines section). +```yaml +# _projects/my-project.md +--- +title: My Awesome Project +description: A sentence or two about what the project is for. +tagline: Because awesomeness is underrated +featured: true # Include in featured projects on hub home page - - Provide your site-wide symbol in <site root>/assets/symbol.svg. +site: + git_repo_url: https://github.com/your-org/my-project-site + git_repo_branch: main - - Provide the symbol as PNG renders as `favicon.png` and `favicon-192x192.png` - under `<site root>/assets/`; use transparent background. +home_url: https://your-org.github.io/my-project/ +tags: [awesome, project, open-source] +--- -- **Site name** displayed to the right of the symbol. - Limit the name to 1-3 words. +Detailed description of the project goes here... +``` - Drop a file called `title.html` in the root of your site. - In its contents you can go as simple as `{{ site.name }}` - and as complex as a custom SVG shape. +### Adding Software - Note that it must look good when placed inside ~30px tall container. - In case of SVG, SVG guidelines apply. +Software components are defined in the `_software` collection: -If you want to style SVG with CSS specifying rules for .site-logo descendants: -take care, as this may cause issues when hub site’s logo is used in context -of a project site. (You can use inline styling within the SVG.) +```yaml +# _software/my-tool.md +--- +title: My Development Tool +description: A powerful tool for developers +repo_url: https://github.com/your-org/my-tool +repo_branch: main # Optional, defaults to site's default_repo_branch + +# Optional: Separate documentation repository +docs: + git_repo_url: https://github.com/your-org/my-tool-docs + git_repo_subtree: docs + git_repo_branch: main -### Blog +tags: [Ruby, CLI, Developer Tools] +external_links: + - url: https://github.com/your-org/my-tool + - url: https://rubydoc.info/gems/my-tool + title: "API Documentation" -Project sites and hub site can have a blog. +feature_with_priority: 1 # Featured on home page +--- -In case of the hub, blog index will show combined timeline -from hub blog and projects’ blogs. +Your software description goes here... +``` -#### Index +**Software Logo**: Place an SVG logo at `_software/my-tool/assets/img/symbol.svg` -Create blog index page as _pages/blog.html, with nothing but frontmatter. -Use layout called "blog-index", pass `hero_include: index-page-hero.html`, -and set `title` and `description` as appropriate for blog index page. +### Adding Specifications -Example: +Specifications are defined in the `_specs` collection: ```yaml +# _specs/my-spec.md --- -title: Blog -description: >- - Get the latest announcements and technical how-to’s - about our software and projects. -layout: blog-index -hero_include: index-page-hero.html ---- -``` +title: My Technical Specification +description: A comprehensive specification for X protocol +tags: [RFC, Standard, Protocol] -#### Posts +external_links: + - url: https://tools.ietf.org/html/rfc1234 + title: "RFC 1234" + +# Optional: Build specification from source +spec_source: + git_repo_url: https://github.com/your-org/spec-repo + git_repo_subtree: specification + git_repo_branch: main + build: + engine: png_diagram_page + options: + format: png -In general, posts are authored as per usual Jekyll setup. +# Optional: Custom navigation for built specs +navigation: + sections: + - name: "Diagrams" + items: + - title: "Overview Diagram" + path: "overview" + description: "System overview" +--- -It is recommended that you provide explicit hand-crafted post excerpts, -as automatically-generated excerpts may break the post card layout. +Your specification description goes here... +``` -Theme also anticipates author information within frontmatter. -Together with excerpts, here’s how post frontmatter (in addition to anything -already required by Jekyll) looks like: +### Adding Custom Content Types (e.g., Advisories) + +You can extend Prexian with custom collections: + +1. **Define the collection** in `_config.yml`: + ```yaml + collections: + advisories: + output: true + permalink: /advisories/:path/ + + defaults: + - scope: + path: _advisories + type: advisories + values: + layout: advisory # Create this layout + ``` + +2. **Create content** in `_advisories/`: + ```yaml + # _advisories/security-advisory-001.md + --- + title: Security Advisory 001 + severity: high + date: 2024-01-15 + affected_versions: ["< 2.1.0"] + tags: [security, vulnerability] + --- + + Description of the security issue... + ``` + +3. **Create an index page** in `_pages/advisories.html`: + ```yaml + --- + title: Security Advisories + layout: advisory-index # Create this layout + hero_include: index-page-hero.html + --- + ``` + +### Hero Sections and Custom Introductions + +#### Adding a Hero Section + +Add a hero section to any page by specifying `hero_include` in the frontmatter: ```yaml --- -# Required -authors: - - email: <author’s email, required> - use_picture: <`gravatar` (default), `assets`, an image path relative to assets/, or `no`> - name: <author’s full name> - social_links: - - https://twitter.com/username - - https://facebook.com/username - - https://linkedin.com/in/username - -# Recommended -excerpt: >- - Post excerpt goes here, and supports inline HTML formatting only. - -# Optional. Cover image. Would normally refer to an illustration from within the post. -# First post, if it has card_image specified, will be displayed with bigger layout -# featuring the image. -card_image: <path, starting with /assets/> +title: My Page +hero_include: index-page-hero.html --- ``` -For hub-wide posts, put posts under _posts/ in site root and name files e.g. -`2018-04-20-welcome-to-jekyll.markdown` (no change from the usual Jekyll setup). - -If ``use_picture`` is set to "assets", author photo would be expected to -reside under `assets/blog/authors/<author email>.jpg`. +#### Custom Introduction Section -For project posts, see below the project site section. +For project sites, you can add a custom introduction section: +1. **Enable it** in `_config.yml`: + ```yaml + prexian: + landing_priority: + - custom_intro + - software + - specs + - blog + ``` -## Hub site +2. **Create the include** file `custom-intro.html`: + ```html + <section class="custom-intro"> + <div class="summary"> + <h2>Welcome to {{ site.title }}</h2> + <p>{{ site.description }}</p> + </div> -The hub represents your company or department, links to all projects -and offers a software and specification index. + <div class="call-to-action"> + <a href="/getting-started" class="btn btn-primary">Get Started</a> + <a href="/documentation" class="btn btn-secondary">Documentation</a> + </div> + </section> + ``` -Note that a hub site is expected to have at least one document -in the `projects` collection (see below). +### Tag Namespaces -Additional items allowed/expected in _config.yml: +Tag namespaces help organize and categorize content with semantic meaning: ```yaml -# Since a hub would typically represent an organization as opposed -# to individual, this would make sense: -seo: - type: Organization - +# _config.yml tag_namespaces: software: - namespace_id: "Human-readable namespace name" - # E.g.: - # writtenin: "Written in" + writtenin: "Written in" # Programming languages + bindingsfor: "Bindings for" # Language bindings + user: "Target user" # Target audience + interface: "Interface" # UI type (CLI, GUI, API) specs: - namespace_id: "Human-readable namespace name" + audience: "Audience" # Who the spec is for + completion_status: "Status" # Draft, Final, etc. + domain: "Domain" # Technical domain ``` -### Project, spec and software data - -Each project subdirectory -must contain a file "index.md" with frontmatter like this: +Use namespaced tags in your content: ```yaml -title: Sample Awesome Project -description: A sentence or two about what the project is for. -tagline: Because awesomeness is underrated - -# Whether the project is included in featured three projects on hub home page -featured: true | false +tags: [writtenin:Ruby, user:Developer, interface:CLI, audience:Technical] +``` -site: - git_repo_url: <Git URL to standalone project site source repo> - git_repo_branch: <branch name in the above repo> +### Collections Configuration -home_url: <URL to standalone project site> +Collections define the types of content your site can contain: -tags: [some, tags] +```yaml +# _config.yml +collections: + # Core Prexian collections + software: + output: true + permalink: /software/:path/ + specs: + output: true + permalink: /specs/:path/ + posts: + output: true + permalink: /blog/:year-:month-:day-:title/ + + # Hub sites only + projects: + output: false + + # Custom collections + advisories: + output: true + permalink: /advisories/:path/ + tutorials: + output: true + permalink: /tutorials/:path/ ``` -### Project index page +### Data Collections -Create software index in _pages/projects.html, with nothing but frontmatter. -Use layout called "project-index", pass `hero_include: index-page-hero.html`, -and set `title` and `description` as appropriate. - -Example: +Prexian supports Jekyll's data collections for structured content. Create YAML files in `_data/`: ```yaml ---- -title: Open projects -description: Projecting goodness into the world! -layout: project-index -hero_include: index-page-hero.html ---- +# _data/team.yml +- name: John Doe + role: Lead Developer + github: johndoe + +- name: Jane Smith + role: Technical Writer + github: janesmith ``` -### Software index page +Use in templates: +```liquid +{% for member in site.data.team %} + <div class="team-member"> + <h3>{{ member.name }}</h3> + <p>{{ member.role }}</p> + </div> +{% endfor %} +``` -Create software index in _pages/software.html, with nothing but frontmatter. -Use layout called "software-index", pass `hero_include: index-page-hero.html`, -and set `title` and `description` as appropriate. +## 🔧 Configuration -Example: +### Essential Configuration ```yaml ---- -title: Software -description: Open-source software developed with MyCompany’s cooperation. -layout: software-index -hero_include: index-page-hero.html ---- -``` +# _config.yml +title: Your Project Name +description: A brief description of your project +url: https://your-project.github.io + +prexian: + title: Your Project Name + description: A brief description of your project + tagline: Your project's tagline + site_type: project # 'project' or 'hub' + + author: "Your Organization" + authors: + - name: Your Name + email: your.email@example.com -### Specification index page + social: + links: + - https://github.com/your-org + - https://twitter.com/your-org -Create spec index in _pages/specs.html, with nothing but frontmatter. -Use layout called "spec-index", pass `hero_include: index-page-hero.html`, -and set `title` and `description` as appropriate. + github_repo_url: https://github.com/your-org/your-project + default_repo_branch: main # Default branch for all repositories +``` -Example: +### Git Repository Configuration + +All Git repository references support branch/version specification: ```yaml ---- -title: Specifications -description: Because specifications are cool! -layout: spec-index -hero_include: index-page-hero.html ---- +# For parent hub +prexian: + hub: + git_repo_url: https://github.com/your-org/hub-site + git_repo_branch: main # or version tag like 'v1.2.3' + +# For software documentation +# _software/my-tool.md +docs: + git_repo_url: https://github.com/your-org/my-tool-docs + git_repo_branch: main # or 'develop', 'v2.0', etc. + +# For specifications +# _specs/my-spec.md +spec_source: + git_repo_url: https://github.com/your-org/spec-repo + git_repo_branch: main # or 'draft', 'v1.0', etc. ``` +## 🏗️ Internal Architecture -## Project site +### Developer Services -For standalone sites of each of your projects, _config.yml should include -site-wide `title` that is the same as project name. +Prexian provides several internal services for developers: -Additional items allowed/expected in _config.yml: +#### `Prexian::Configuration` +Centralized configuration management with validation: +```ruby +config = Prexian::Configuration.new(site.config) +config.site_type # => 'project' or 'hub' +config.hub_site? # => true/false +config.default_repo_branch # => 'main' +``` -```yaml -authors: - - name: Your Name - email: your-email@example.com +#### `Prexian::GitService` +Repository operations with caching: +```ruby +git_service = Prexian::GitService.new +result = git_service.shallow_checkout(repo_url, branch: 'main') +git_service.copy_cached_content(result[:local_path], destination) +``` -author: "Company or Individual Name Goes Here" +#### `Prexian::HubSiteLoader` +Aggregates content from multiple project repositories for hub sites. -# Any given open project site is assumed to be part of a hub, -# and hub details in this format are required to let project site -# reference the hub. -parent_hub: - git_repo_url: git@example.com:path/to-repo.git - git_repo_branch: somebranchname - home_url: https://www.example.com/ +#### `Prexian::ProjectSiteLoader` +Handles individual project site content and parent hub integration. -algolia_search: - api_key: '<your Algolia API key>' - index_name: '<your Algolia index name>' +### The `_hub-site` Directory -# Only add this if you want to use Algolia’s search on your project site. +For project sites with a parent hub, Prexian automatically: -tag_namespaces: - software: - namespace_id: "Human-readable namespace name" - # E.g.: - # writtenin: "Written in" - specs: - namespace_id: "Human-readable namespace name" -# NOTE: Tag namespaces must match corresponding hub site’s configuration entry. - -landing_priority: [custom_intro, blog, specs, software] -# Which order should sections be displayed on landing. -# -# Default order: [software, specs, blog] -# Depending on your project’s focus & pace of development you may want to change that. -# Supported sections: featured_posts, featured_software, featured_specs, custom_intro. -# -# If you use custom_intro, project site must define an include "custom-intro.html". -# The contents of that include will be wrapper in section.custom-intro tag. -# Inside the include you’d likely want to have introductory summary wrapped -# in section.summary, and possibly custom call-to-action buttons -# (see Metanorma.com site for an example). -``` +1. **Fetches hub content** from the parent hub repository +2. **Copies it** to `_hub-site/hub/` in your project site +3. **Adds it to include paths** so you can reference hub assets -### File structure - -Each project is expected to have a machine-readable and unique name, a title, -a description, a symbol, one or more software products and/or specs. -Blog, docs, and other pages are optional. - -Following data structure is used for project sites: - - - <project-name>/ # Jekyll site root containing _config.yml - - assets/ - - symbol.svg # Required — project logo - - _software/ - - <name>.adoc - - <name>/ - - assets/ - - symbol.svg - - _specs/ - - <name>.adoc - - _pages/ - - blog.html - - software.html # Software index - - specs.html # Spec index - - docs.html - - docs/ # Project-wide documentation - - getting-started.adoc - - <some-page>.adoc - - _posts/ # Blog - - 2038-02-31-blog-post-title.markdown - - _layouts/ - - docs.html - -### Blog - -Author project site blog posts as described in the general site setup section. - -### Project docs - -Two kinds of docs can coexist on a given open project site: - -- Project-wide documentation. It’s well-suited for describing the idea behind the project, - the “whys”, for tutorials and similar. -- Documentation specific to a piece of software (of which there can be more than one - for any given open project). This may go in detail about that piece of software, - and things like implementation specifics, extended installation instructions, - development guidelines may go here. - -This section is about project-wide docs, for software docs see software and specs section. - -The suggested convention is to create -_pages/docs.adoc for the main documentation page, put other pages under docs/, -and create custom layout `docs.html` that inherits from `docs-base`, specifies -`html-class: docs-page` and provides `navigation` structure linking to all docs pages -in a hierarchy. - -Example _layouts/docs.html: +This allows project sites to: +- Use the hub's logo: `{% include hub/assets/img/symbol.svg %}` +- Reference hub branding: `{% include hub/title.html %}` +- Maintain consistent styling with the hub +The `_hub-site` directory structure: ``` ---- -layout: docs-base -html-class: docs-page -docs_title: <Project name> -navigation: - items: - - title: Introduction - items: - - title: "Overview" - path: /docs/ - - title: "Get started" - path: /docs/getting-started/ ---- - -{{ content }} +_hub-site/ +└── hub/ + ├── assets/ + │ └── symbol.svg + ├── title.html + └── other-hub-includes.html ``` -Example _pages/docs.adoc: +## 🎨 Customization -``` +### Styling + +Create custom styles in `assets/css/style.scss`: + +```scss --- -layout: docs -title: Overview -html-class: >- - overview - # ^^ classes you can use to style the page in your custom CSS rules --- -:page-liquid: -Your main docs page goes here. +@import "prexian/custom-variables"; +@import "prexian"; +@import "prexian/custom-styles"; + +// Your custom styles here +.custom-class { + color: #your-color; +} ``` -### Software and specs +### Layouts and Includes -An open project serves as an umbrella for related -software products and/or specifications. +Override theme layouts by creating files in `_layouts/` and includes in your site root (not `_includes/`): -Each product or spec is described by its own <name>.adoc file with frontmatter, -placed under _software/ or _specs/ subdirectory (respectively) -of your open project’s Jekyll site. +- `title.html` - Custom site title/logo +- `custom-intro.html` - Custom introduction section +- `project-nav.html` - Additional navigation links -A software product additionally is required to have a symbol in SVG format, -placed in <name>/assets/symbol.svg within _software/ directory. +### Logo and Symbol Guidelines -YAML frontmatter that is expected with both software and specs: +#### Site Symbol +- **Format**: SVG +- **Size**: Should look good at 30x30px and 60x60px +- **Location**: `assets/img/symbol.svg` +- **Requirements**: + - No IDs in SVG markup (appears multiple times on page) + - Root `<svg>` element must have `viewBox` attribute + - No `width` or `height` attributes on root element -```yaml -title: A Few Words -# Shown to the user -# and used for HTML metadata if jekyll-seo-tag is enabled - -description: A sentence. -# Not necessarily shown to the user, -# but used for HTML metadata if jekyll-seo-tag is enabled - -tags: [Ruby, Python, RFC, "<some_namespace_id>:<appropriate_tag>"] -# NOTE: Avoid whitespaces and other characters that may make Jekyll -# percent-encode the tag in URLs. Replace " " (a regular space) -# with "_" (underline); underlines will be rewritten as spaces when tags -# are presented to site users. -# Tag can be prepended with a namespace to signify the type, -# e.g. chosen programming language or target viewer audience -# (see hub site configuration for tag namespace setup). -# Avoid long namespace/tag combos as they can overflow item’s card widget. +#### Favicon +Provide PNG renders as: +- `assets/favicon.png` +- `assets/favicon-192x192.png` -external_links: - - url: https://github.com/metanorma/metanorma - - url: https://docs.rs/proj/ver/…/ - - { url: https://example.com/, title: "Custom title" } -# External links. -# For software, typically points to docs sites or source code repository. -# For specs, this usually contains RFC, IETF links, spec source code. -# * Link label can be specified with the title key. -# Select URLs are recognized and an appropriate label -# (possibly icon) is shown by default, -# otherwise you **should** specify the title. -# Currently, recognized URLs include -# GitHub, Docs.rs, RubyDoc, -# ietf.org/html/rfcN, datatracker.ietf.org/doc/… -# * Order links according to importance for project site visitors. -# The first link will be highlighted as primary. - -feature_with_priority: 1 -# With this key, software or spec will be featured on home -# page of project site. Lower number means higher priority -# (as in, priority no. 1 means topmost item on home page, -# as long as there aren’t others with the same value). -# If no documents in the collection have this key, -# items on home will be ordered according to Jekyll’s -# default behavior. -``` +Use transparent background for PNG files. -### Software product +## 🔧 CLI Tools -YAML frontmatter required for software: +Prexian includes command-line tools for cache management: -```yaml -repo_url: https://github.com/riboseinc/asciidoctor-rfc -# Required. -# Used for things like showing how long ago -# the was project updated last. +```bash +# Check version +bundle exec prexian version -repo_branch: main +# Clear cache +bundle exec prexian cache clear -docs_source: - git_repo_url: git@example.com:path/to-repo.git - git_repo_subtree: docs - git_repo_branch: main -# Documentation, the contents of which will be made part of the project site. -# See the nearby section about documentation. +# Show cache status +bundle exec prexian cache status ``` -#### Displaying software docs +## 🐛 Debugging and Troubleshooting -Inside the repository and optionally subtree specified under `docs` -in above sample, place a file `navigation.adoc` (or `navigation.md`) containing -only frontmatter, following this sample: +### Building this site itself -```yaml ---- -items: -- title: Introduction - path: intro/ - items: - - { title: Overview, path: intro/overview/ } - - { title: Installation, path: intro/installation/ } -- { title: Usage, path: usage/ } ---- +The Prexian site itself is a project site of the ribose.com hub site. -= Navigation +```bash +./script/build +./script/test +./script/server ``` -In the same directory, place the required document pages—in this case, `overview.adoc`, -`installation.adoc`, and `basic-usage.adoc`. Each file must contain -standard YAML frontmatter with at least `title` specified. - -During project site build, Jekyll pulls docs for software that’s part of the -site and builds them, converting pages from Markdown/AsciiDoc to HTML and adding -the navigation. - +### Testing Site Builds -### Specification +Use the provided test scripts to verify your site works correctly: -YAML frontmatter specific to specs: +#### Test Hub Sites +```bash +# Test hub site functionality +./script/test-hub -```yaml -spec_source: - git_repo_url: https://github.com/<user>/<repo> - git_repo_subtree: images - git_repo_branch: main - build: - engine: png_diagrams -# See below about building the spec from its source -# to be displayed on the site. +# Build hub site for production +./script/build-hub -navigation: - sections: - - name: Model diagrams - items: - - title: "CSAND Normal Document" - path: "Csand_NormalDocument" - description: "" - ignore_missing: yes +# Verify these elements are present: +# - Project software components from all projects +# - Project specifications from all projects +# - Project blog posts alongside hub posts +# - Proper navigation and layout ``` -#### Displaying specification contents - -While software doc pages are currently simply generated using standard -Jekyll means from Markdown/AsciiDoc into HTML, -building specs is handled in a more flexible way, -delegating the source -> Open Project site-compatible HTML conversion -to an engine. +#### Test Project Sites +```bash +# Test project site functionality +./script/test-project -For specs to be built, provide build config and navigation -in the YAML frontmatter of corresponding `_specs/<specname>.adoc` file -as described in spec YAML frontmatter sample. +# Build project site for production +./script/build-project -For now, only the `png_diagrams` engine is supported, with Metanorma-based -project build engine to come. - -During project site build, Jekyll pulls spec sources that’s part of the -site and builds them, converting pages from source markup to HTML using -the engine specified, and adding the navigation. - -### Symbol +# Verify these elements are present: +# - Parent hub logo/link in footer +# - Proper /specs/ page layout and content +# - All project-specific content (software, specs, blog) +# - Consistent styling with parent hub +``` -Should look OK in dimensions of about 30x30, 60x60px. Must fit in a square. -Should be in SVG format (see also the SVG guidelines section). -Place the symbol in assets/symbol.svg within project directory. +### Common Debugging Steps +#### 1. Check Configuration +Ensure your `_config.yml` has the required settings: -## SVG guidelines +```yaml +# For project sites +prexian: + site_type: project + hub: + git_repo_url: https://github.com/your-org/hub-site + git_repo_branch: main + home_url: https://your-hub-site.com/ + +# For hub sites +prexian: + site_type: hub + +collections: + projects: + output: false +``` -- Ensure SVG markup does not use IDs. It may appear multiple times - on the page hence IDs would fail markup validation. -- Ensure root `<svg>` element specifies the `viewBox` attribute, - and no `width` or `height` attributes. -- You can style SVG shapes by adding custom rules to site’s assets/css/style.scss. -- Project symbols only: the same SVG is used both in hub site’s project list - (where it appears on white, and is expected to be colored) - and in project site’s top header - (where it appears on colored background, and is expected to be white). - It is recommended to use a normal color SVG, and style it in project site’s - custom CSS. The SVG must be created in a way that allows this to happen. +#### 2. Verify Repository Access +Check that all Git repositories are accessible: +```bash +# Test hub repository access (for project sites) +git ls-remote https://github.com/your-org/hub-site -## Content guidelines +# Test project repository access (for hub sites) +git ls-remote https://github.com/your-org/project-site +``` -- Project, software, spec title: 1-3 words, capital case -- Project, software, spec description: about 12 words, no markup -- Project description (featured): about 20-24 words, no markup -- Blog post title: 3–7 words -- Blog post excerpt: about 20–24 words, no markup +#### 3. Clear Cache +If you're seeing stale content: +```bash +bundle exec prexian cache clear +bundle exec jekyll clean +``` -## Authoring content +#### 4. Check Build Output +Look for error messages during build: -Content is expected to be authored in AsciiDoc. -Some features, such as in-page navigation in software/project documentation -and code listing copy buttons, -require HTML structure to match the one generated from AsciiDoc by jekyll-asciidoc -and won’t work with content is authored in Markdown, for example. +```bash +bundle exec jekyll build --verbose +``` -### Disabling copy button on code listings +#### 5. Inspect Generated Files +Check that content is being loaded correctly: -By default, each code listing widget, like below, will have a copy button -next to the `<pre>` element. +```bash +# For project sites, verify hub content is loaded +ls -la _hub-site/hub/ -``` -[source,sh] ----- -docker pull ribose/metanorma ----- +# For hub sites, verify project content is loaded +ls -la _project-sites/ ``` -To disable that button for a particular listing, add `.nocopy` class to it: +### Common Issues and Solutions -``` -[.nocopy] -[source,sh] ----- -docker pull ribose/metanorma ----- -``` +#### Hub Sites Not Showing Project Content +**Symptoms**: Hub site builds successfully but doesn't display software, specifications, or blog posts from projects. -## Theme includes +**Check**: +- Verify project definitions in `_projects/` directory +- Ensure project repositories are accessible +- Check that project sites have the correct `site_type: project` configuration +- Clear cache and rebuild: `bundle exec prexian cache clear && bundle exec jekyll build` -Commonly used overridable includes are (paths relative to your site root): +#### Project Sites Missing Hub Assets +**Symptoms**: Project site builds but missing parent hub logo, branding, or styling. -- title.html: Site name in case you want to provide custom typography, - possibly as SVG. +**Check**: +- Verify `hub` configuration in `_config.yml` +- Ensure hub repository is accessible +- Check that hub site has the correct `site_type: hub` configuration +- Verify `_hub-site/hub/` directory is created during build -- project-nav.html (currently project sites only): Additional - links in project site’s top navigation, if needed. +#### Broken `/specs/` Page Layout +**Symptoms**: Specifications page loads but has incorrect styling or missing content. -- assets/symbol.svg: Site-wide symbol is used as an include - to facilitate path fill color overrides via CSS rules. +**Check**: +- Verify specifications are defined in `_specs/` collection +- Check that `specs` collection is configured in `_config.yml` +- Ensure specification files have correct frontmatter +- Verify `spec-index` layout is available -### Include location gotcha +### Path Configuration -Theme configuration adds `includes_dir: .` to your site. -This means when Jekyll encounters `{% include <include_name> %}` -in a template, it looks first in `<site root>/<include_name>`, -and then in `<theme root>/_includes/<include_name>`. Consequently, -you put your include overrides directly in site root, **not** inside -`_includes/` directory of your site. +All paths for software, specifications, and projects should use **relative paths** from the repository root, not absolute filesystem paths. +**Correct**: +```yaml +# _software/my-tool.md +docs: + git_repo_url: https://github.com/your-org/my-tool + git_repo_subtree: docs # Relative path within the repository -## Theme layouts +# _specs/my-spec.md +spec_source: + git_repo_url: https://github.com/your-org/spec-repo + git_repo_subtree: specification # Relative path within the repository +``` -Normally you don’t need to specify layouts manually, except where -instructed in site setup sections of this document. +**Incorrect**: +```yaml +# Don't use absolute paths +docs: + git_repo_subtree: /Users/username/projects/docs # ❌ Absolute path +``` -Commonly used layouts are: +### Performance Tips -- blog-index: Blog index page. Pages using this layout are recommended - to supply hero_include. +- Use `git_repo_branch` to pin to specific versions for stability +- Use shallow clones (automatic) for faster builds +- Clear cache periodically: `bundle exec prexian cache clear` +- Use `bundle exec jekyll serve --incremental` for faster development builds -- post: Blog post. +### Development Workflow -- project-index: Open project index page (hub site only). - Suggested to supply hero_include. - Will show a list of open projects across the hub. +When developing themes or debugging issues: -- software-index: Software index page (hub site only). - Suggested to supply hero_include. - Will show a list of software across projects within the hub. +1. **Use test scripts**: `./script/test-hub` or `./script/test-project` +2. **Check logs**: Look for error messages in Jekyll build output +3. **Inspect generated files**: Verify content is loaded in `_hub-site/` or `_project-sites/` +4. **Clear cache**: Use `bundle exec prexian cache clear` when switching between configurations +5. **Test incrementally**: Make small changes and test frequently -- spec-index: Specification index page (hub site only). - Suggested to supply hero_include. - Will show a list of specs across projects within the hub. +## 🛠️ Theme Development -- product: Software product (project site only). +### Building the Theme -- spec: Open specification (project site only). +When developing the Prexian theme itself, you cannot use the standard `_config.yml` file because it gets inherited by user gems. Instead, use one of these methods: -- default: Main layout; among other things adds `html-class` specified in frontmatter - of last inheriting layout and the concrete page frontmatter to the `<body>` element. +#### Method 1: Use the Theme Development Configuration -### Page frontmatter +```bash +# Build with theme development config +bundle exec jekyll build --config _config_theme-dev.yml -Typical expected page frontmatter is `title` and `description`. Those are -also used by jekyll-seo-tag plugin to add the appropriate meta tags. +# Serve with theme development config +bundle exec jekyll serve --config _config_theme-dev.yml +``` -Commonly supported in page frontmatter is the hero_include option, -which would show hero unit underneath top header. -Currently, theme supports _includes/index-page-hero.html as the only value -you can pass for hero_include (or you can leave hero_include out altogether). +#### Method 2: Use the Build Script +```bash +# Build the example site +script/build -## Style customization +# Serve the example site +script/server +``` -To customize site appearance, create a file in your Jekyll site -under assets/css/style.scss with following exact contents: +The `_config_theme-dev.yml` file contains the proper configuration for testing the theme with example content, while `_config.yml` is kept minimal for gem users. -``` ---- ---- -// Font imports can go here +### Configuration Files -// Variable redefinitions can go here +- **`_config.yml`**: Minimal configuration inherited by gem users +- **`_config_theme-dev.yml`**: Full development configuration with example content +- **`script/build`**: Convenience script that uses the theme development config +- **`script/server`**: Development server script -@import 'jekyll-theme-rop'; +### Testing Changes -// Custom rules can go here -``` +After making changes to the theme: -There are two aspects to theme customization: +1. **Test the build**: + ```bash + script/build + ``` -* Cutomize SASS variables before the import (such as colors) -* Define custom style rules after the import +2. **Test the CLI tools**: + ```bash + bundle exec prexian version + bundle exec prexian cache status + ``` -### Custom rules +3. **Run the test suite**: + ```bash + bundle exec rspec + ``` -One suggested custom rule would be to change the fill color for SVG paths -used for your custom site symbol to white, unless it’s white by default. +4. **Serve locally to verify**: + ```bash + script/server + ``` -The rule would look like this: +## 📝 Content Authoring -```scss -.site-logo svg path { - fill: white; -} -``` +### AsciiDoc Support -### SASS variables +Content is expected to be authored in AsciiDoc for full feature support: -Following are principal variables that define the appearance of a site -built with this theme, along with their defaults. +```adoc +--- +title: My Documentation Page +layout: docs-base +--- +:page-liquid: -For a project site, wisely choosing primary and accent colors should be enough -as a minimum. += My Documentation -```scss -$font-family: Helvetica, Arial, sans-serif !default; -$main-font-color: black !default; +== Introduction -# Primary color & accent colors are used throughout site’s UI. -# Make sure to use shades dark enough that white text is readable on top, -# especially with the primary color. -# Make sure these colors go well with each other. -$primary-color: lightblue !default; -$accent-color: red !default; +This is an AsciiDoc document with full Prexian support. -# These colors are used for warning/info blocks within body text. -$important-color: orange !default; -$warning-color: red !default; +[source,ruby] +---- +puts "Hello, World!" +---- +``` -# Background used on home page body & other pages’ hero unit backgrounds. -$main-background: linear-gradient(315deg, $accent-color 0%, $primary-color 74%) !default; +### Disabling Copy Buttons -# This background defaults to $main-background value. -$header-background: $main-background !default; +By default, code listings have copy buttons. To disable for a specific listing: +```adoc +[.nocopy] +[source,sh] +---- +some command +---- +``` -# Below does not apply to project sites (only the hub site): +### Blog Posts -$hub-software--primary-color: lightsalmon !default; -$hub-software--primary-dark-color: tomato !default; -$hub-software--hero-background: $hub-software--primary-dark-color !default; +```yaml +--- +layout: post +title: "Announcing Version 2.0" +date: 2024-01-15 +authors: + - name: John Doe + email: john@example.com + use_picture: gravatar # 'gravatar', 'assets', 'no', or path + social_links: + - https://github.com/johndoe + - https://twitter.com/johndoe +excerpt: >- + We're excited to announce the release of version 2.0 with many new features. +card_image: /assets/blog/v2-announcement.png # Optional cover image +--- -$hub-specs--primary-color: lightpink !default; -$hub-specs--primary-dark-color: palevioletred !default; -$hub-specs--hero-background: $hub-specs--primary-dark-color !default; +Your blog post content goes here... ``` -TIP: A good way to find a good match for primary-color and accent-color -may be the eggradients.com website. Find a suitable, dark enough gradient and pick -one color as primary, and the other as accent. +## 🔄 Migration from jekyll-theme-open-project +If you're migrating from the old `jekyll-theme-open-project`: -## Extra .gitignore rules +1. **Update Gemfile**: + ```ruby + # Remove old gems + # gem "jekyll-theme-open-project" + # gem "jekyll-theme-open-project-helpers" -Add these lines to your .gitignore to prevent -theme-generated files and directories from adding chaos to your Git staging. + # Add new gem + gem "prexian" + ``` -``` -_software/*/.git -_software/*/docs -_software/_*_repo -_specs/*/ -!_specs/*.* -parent-hub/* -``` +2. **Update _config.yml**: + ```yaml + # Remove old plugins + plugins: + - prexian # Replace all old plugins with this + # Update site type configuration + prexian: + site_type: hub # instead of is_hub: true + ``` -## Contributing +3. **Update SCSS imports**: + ```scss + // Old + @import 'jekyll-theme-open-project'; -Bug reports and pull requests are welcome on GitHub -at https://github.com/riboseinc/jekyll-theme-rop. + // New + @import 'prexian'; + ``` -This project is intended to be a safe, welcoming space for collaboration, -and contributors are expected to adhere -to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. +4. **Update engine references**: + ```yaml + # Old + engine: png_diagrams + # New + engine: png_diagram_page + ``` -## Theme development +5. **Make sure you have required files**: + - `assets/img/symbol.svg` + - `title.html` -Generally, this directory is setup like a Jekyll site. To set it up, -run `bundle install`. +## 🌟 Examples -To experiment with this code, add content (projects, software, specs) -and run `bundle exec jekyll serve`. This starts a Jekyll server -using this theme at `http://localhost:4000`. +See Prexian in action: -Put your layouts in `_layouts`, your includes in `_includes`, -your sass files in `_sass` and any other assets in `assets`. +- [Metanorma](https://www.metanorma.org) - Standards authoring platform +- [RNP](https://www.rnpgp.org) - OpenPGP implementation +- [Relaton](https://www.relaton.org) - Bibliographic data toolkit -Add pages, documents, data, etc. like normal to test your theme's contents. +## 🤝 Contributing -As you make modifications to your theme and to your content, your site will -regenerate and you should see the changes in the browser after a refresh, -like normal. +Bug reports and pull requests are welcome on GitHub at https://github.com/riboseinc/prexian. -When your theme is released, only files specified with gemspec file -will be included. If you modify theme to add more directories that -need to be included in the gem, edit regexp in the gemspec. +## 📄 License -### Building and releasing +The theme is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). -#### Manual test during development +## 🆘 Support -When you’re working on visual aspects of the theme, it’s useful -to see how it would affect the end result (a site *built with* this theme). +- **Documentation**: Full documentation available in this README +- **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/riboseinc/prexian/issues) +- **Discussions**: Join the conversation on [GitHub Discussions](https://github.com/riboseinc/prexian/discussions) -Here’s how to develop the theme while simultaneously previewing the changes -on a site. The sequence would be as follows, assuming you have a local copy -of this repo and have a Jekyll site using this theme: +--- -1. For the Jekyll site, change Gemfile to point to local copy - of the theme (the root of this repo) and run `bundle`. +## Detailed Configuration Reference - For example, you’d change `gem "jekyll-theme-rop", "~> 1.0.6"` - to `gem "jekyll-theme-rop", :path => "../jekyll-theme-rop"`. - The relative path assumes your site root and theme root are sibling directories. +<details> +<summary>Click to expand complete configuration options</summary> -2. Run `bundle exec jekyll serve` to start Jekyll’s development server. +### Complete Configuration Example -3. Make changes to both theme and site directory contents. +```yaml +# _config.yml +title: Your Project +description: Project description +url: https://your-project.github.io -4. If needed, kill with Ctrl+C then relaunch the serve command - to apply the changes you made to the theme - (it may not reload automatically if changes only affect the theme and not the site - you’re serving). +prexian: + title: Your Project + description: Project description + tagline: Your project tagline -4. Once you’re satisfied, release a new version of the theme — see below. + # Site type (required) + site_type: project # 'project' or 'hub' -5. (To later bump the site to this latest version: revert the Gemfile change, - update theme dependency version to the one you’ve just released, - run `bundle --full-index` to update lockfile properly, - and your site is ready to go.) + # Repository settings + default_repo_branch: main + github_repo_url: https://github.com/your-org/your-project + github_repo_branch: main -#### Releasing + # Author information + author: "Your Organization" + authors: + - name: Your Name + email: your.email@example.com + contact_email: your.email@example.com -Make sure theme works: build script is under construction, -so use good judgement and thorough manual testing. + # Parent hub (for project sites) + hub: + git_repo_url: https://github.com/your-org/hub-site + git_repo_branch: main + home_url: https://your-org.github.io/ -1. Decide whether this is a patch, minor or major change. + # Social links + social: + links: + - https://github.com/your-org + - https://twitter.com/your-org -2. Run the automated "release" workflow to release the gem. + # Legal information + legal: + name: Your Organization + tos_link: https://your-org.com/terms + privacy_policy_link: https://your-org.com/privacy + + # Search (optional) + algolia_search: + api_key: 'your-api-key' + index_name: 'your-index' + + # Landing page sections order + landing_priority: + - software + - specs + - blog + - custom_intro + + # Call-to-action buttons + home_calls_to_action: + - url: "/getting-started" + title: "Get Started" + - url: "/documentation" + title: "Documentation" + +# Tag namespaces +tag_namespaces: + software: + writtenin: "Written in" + user: "Target user" + interface: "Interface" + specs: + audience: "Audience" + completion_status: "Status" +# Jekyll collections (required) +collections: + projects: # Only for hub sites + output: false + software: + output: true + permalink: /software/:path/ + specs: + output: true + permalink: /specs/:path/ + posts: + output: true + permalink: /blog/:year-:month-:day-:title/ + pages: + output: true + permalink: /:name/ + +# Jekyll defaults (required) +defaults: + - scope: + path: "" + values: + layout: default + - scope: + path: _posts + type: posts + values: + layout: post + - scope: + path: _software + type: software + values: + layout: product + - scope: + path: _specs + type: specs + values: + layout: spec + +# Plugins +plugins: + - prexian + +# Exclude files +exclude: + - .git + - Gemfile* + - README.* + - vendor + - script +``` -#### Testing with build script (TBD) +### Theme Layouts -May not work at the moment — see #26. Please use the other test option. +Available layouts: -To check your theme, run: +- **`default`**: Main layout with `html-class` support +- **`home`**: Homepage layout +- **`post`**: Blog post layout +- **`product`**: Software product layout +- **`spec`**: Specification layout +- **`blog-index`**: Blog index page +- **`software-index`**: Software index page (hub sites) +- **`spec-index`**: Specification index page (hub sites) +- **`project-index`**: Project index page (hub sites) +- **`docs-base`**: Documentation base layout +- **`page`**: Generic page layout - ./develop/build +### Theme Includes -It’ll build Jekyll site and run some checks, like HTML markup validation. +Commonly overridden includes: +- **`title.html`**: Site name/logo +- **`custom-intro.html`**: Custom introduction section +- **`project-nav.html`**: Additional navigation links +- **`assets/img/symbol.svg`**: Site symbol +- **`head.html`**: Custom head content +- **`scripts.html`**: Custom JavaScript -## License +### Content Guidelines -The theme is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). +- **Project/Software/Spec titles**: 1-3 words, title case +- **Descriptions**: ~12 words, no markup +- **Featured descriptions**: ~20-24 words, no markup +- **Blog post titles**: 3-7 words +- **Blog post excerpts**: ~20-24 words, no markup +- **Taglines**: Short, memorable phrases + +</details> diff --git a/Rakefile b/Rakefile index c93ff99..82bb534 100644 --- a/Rakefile +++ b/Rakefile @@ -1,9 +1,8 @@ # frozen_string_literal: true require 'bundler/gem_tasks' -# require "rspec/core/rake_task" +require 'rspec/core/rake_task' -# Uncomment to enable the "spec" task -# RSpec::Core::RakeTask.new(:spec) -# -# task :default => :spec +RSpec::Core::RakeTask.new(:spec) + +task default: :spec diff --git a/_config.yml b/_config.yml index 3b20494..a8053a7 100644 --- a/_config.yml +++ b/_config.yml @@ -1,50 +1,121 @@ -markdown: kramdown +## Prexian site configuration to be filled +## Default is a project site -includes_dir: . +# url: # URL for the site, e.g. "https://prexian.example.com" -permalink: /blog/:year-:month-:day-:title/ +## Development settings -# algolia_search: -# api_key: '' -# index_name: '' -# Uncomment this if you want to use Algolia’s search. -# It’s free for open-source projects. +# incremental: true +# livereload: true -exclude: - - home-hero.html - - title.html - - nav-links.html - - flavor-sample-summary.html - - project-nav.html - - /**/.git/* - - /_projects/**/_*_repo/* - - /_projects/*/assets/css/* - - /_projects/**/docs/* - - .sass-cache/ +prexian: + ## TO CHANGE FOR HUB SITE + site_type: project -external_links: - selector: 'body.site--project main a, body.site--hub.layout--post main a' - ignored_selectors: - - .layout--home a - - a[href*=travis] - - a[href*=coverity] - - a[href*=codecov] + ## TO SET FOR PROJECT SITE + # hub: + # git_repo_url: https://github.com/riboseinc/open.ribose.com + # home_url: https://open.ribose.com/ -landing_priority: [software, specs, blog] -plugins: - - jekyll-theme-rop - - jekyll-seo-tag - - jekyll-sitemap - - jekyll-data - - jekyll-asciidoc - - jekyll-redirect-from - - kramdown-parser-gfm - - kramdown-syntax-coderay + ## TO SET + # title: Prexian + # description: Managing a nexus of open-source software and specification projects. + ## The above two are used by jekyll-seo-tag for things such as + ## `<title>` and `<meta>` tags, as well as elsewhere by the theme. + + ## TO SET + # tagline: >- + # Managing a nexus of open-source software and specification projects. + + ## TO SET + # pitch: >- + # Prexian provides a Jekyll theme (with accompanying plugin code) aiming to help + # organizations and individuals present open-source software and specifications in + # a navigable and elegant way. + + ## TO SET + # author: "Ribose Inc." + + ## TO SET + # authors: + # - name: Ribose Inc. + # email: open.source@ribose.com + # contact_email: open.source@ribose.com + + permalink: /blog/:year-:month-:day-:title/ + + # algolia_search: + # api_key: '0193b06d928ee52f653c6e5ea95d9f97' + # index_name: 'rnpgp' + + landing_priority: + - software + - specs + - blog + + external_links: + selector: 'body.site--project main a, body.site--hub.layout--post main a' + ignored_selectors: + - .layout--home a + - a[href*=travis] + - a[href*=coverity] + - a[href*=codecov] + + fontawesome_cdn: + version: v5.8.1 + integrity: "sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" + # Only applies if no_auto_fontawesome is not set. + + no_auto_fontawesome: false + # If set to yes, site (with default design) must specify <script> elements + # that make required FA styles available in SVG mode. + + # Debug mode for development + # debug: true + + ## TO SET + # social: + # links: + # - {social link 1 URL} + # - {social link 2 URL} + + ## TO SET + # legal: + # name: Ribose + # tos_link: https://www.ribose.com/terms-of-service + # privacy_policy_link: https://www.ribose.com/privacy-policy + ## TO SET + # home_calls_to_action: + # - { url: "/about", title: "About Prexian" } + + ## TO SET + # github_repo_url: https://github.com/riboseinc/prexian + +## CAN BE SET +# tag_namespaces: +# software: +# writtenin: "Written in" +# bindingsfor: "Bindings for" +# user: "Target user" +# interface: "Interface" +# specs: +# audience: "Audience" +# completion_status: "Status" + +# Jekyll configuration +# includes_dir: '_includes' + +## Prexian site configuration defaults + +# Default is a project site +# Must be set for Prexian collections: + ## TO CHANGE FOR HUB SITE projects: output: false + software: output: true permalink: /software/:path/ @@ -53,21 +124,16 @@ collections: permalink: /specs/:path/ posts: output: true - permalink: /blog/:month-:day-:year/:title/ + permalink: /blog/:year-:month-:day-:title/ pages: output: true permalink: /:name/ -fontawesome_cdn: - version: v5.8.1 - integrity: "sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" -# Only applies if no_auto_fontawesome is not set. - -no_auto_fontawesome: false -# If set to yes, site (with default design) must specify <script> elements -# that make required FA styles available in SVG mode. - +# Must be set for Prexian defaults: + # Theme defaults. + # MUST be duplicated from theme’s _config.yml + # (does not get inherited, unlike the collections hash) - scope: path: "" values: @@ -87,3 +153,41 @@ defaults: type: specs values: layout: spec + +markdown: kramdown + +exclude: + - /**/.git/* + - /_projects/**/_*_repo/* + - /_projects/*/assets/css/* + - /_projects/**/docs/* + - README.* + - lib* + - prexian.gemspec + - exe* + - develop + - bin + - .sass-cache/ + - .jekyll-cache/ + - gemfiles/ + - Gemfile + - Gemfile.lock + - node_modules/ + - vendor/bundle/ + - vendor/cache/ + - vendor/gems/ + - vendor/ruby/ + - '**/*.rb' + - script + - Rakefile + +plugins: + - prexian + - jekyll-seo-tag + - jekyll-sitemap + - jekyll-data + - jekyll-asciidoc + - jekyll-redirect-from + - kramdown-parser-gfm + - kramdown-syntax-coderay + diff --git a/_config_prexian.yml b/_config_prexian.yml new file mode 100644 index 0000000..ea41bbf --- /dev/null +++ b/_config_prexian.yml @@ -0,0 +1,178 @@ +# Hub site configuration for testing +url: "https://riboseinc.github.io/prexian" + +markdown: kramdown + +exclude: + - home-hero.html + - title.html + - nav-links.html + - flavor-sample-summary.html + - project-nav.html + - /**/.git/* + - /_projects/**/_*_repo/* + - /_projects/*/assets/css/* + - /_projects/**/docs/* + - README.* + - lib* + - prexian.gemspec + - exe* + - develop + - bin + - .sass-cache/ + - .jekyll-cache/ + - gemfiles/ + - Gemfile + - Gemfile.lock + - node_modules/ + - vendor/bundle/ + - vendor/cache/ + - vendor/gems/ + - vendor/ruby/ + - '**/*.rb' + - script + - Rakefile + - spec/ + +plugins: + - prexian + - jekyll-seo-tag + - jekyll-sitemap + - jekyll-data + - jekyll-asciidoc + - jekyll-redirect-from + - kramdown-parser-gfm + - kramdown-syntax-coderay + +# Prexian site configuration +prexian: + title: Prexian + description: Managing a nexus of open-source software and specification projects. + # The above two are used by jekyll-seo-tag for things such as + # `<title>` and `<meta>` tags, as well as elsewhere by the theme. + + permalink: /blog/:year-:month-:day-:title/ + + # algolia_search: + # api_key: '0193b06d928ee52f653c6e5ea95d9f97' + # index_name: 'rnpgp' + + landing_priority: + - software + - custom_intro + - specs + - blog + + external_links: + selector: 'body.site--project main a, body.site--hub.layout--post main a' + ignored_selectors: + - .layout--home a + - a[href*=travis] + - a[href*=coverity] + - a[href*=codecov] + + fontawesome_cdn: + version: v5.8.1 + integrity: "sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" + # Only applies if no_auto_fontawesome is not set. + + no_auto_fontawesome: false + # If set to yes, site (with default design) must specify <script> elements + # that make required FA styles available in SVG mode. + + tagline: >- + Managing a nexus of open-source software and specification projects. + + pitch: >- + Prexian provides a Jekyll theme (with accompanying plugin code) aiming to help + organizations and individuals present open-source software and specifications in + a navigable and elegant way. + + author: "Ribose Inc." + + authors: + - name: Ribose Inc. + email: open.source@ribose.com + contact_email: open.source@ribose.com + + site_type: project # 'project' or 'hub' + # Debug mode for development + debug: true + + hub: + git_repo_url: https://github.com/riboseinc/open.ribose.com + home_url: https://open.ribose.com/ + + social: + links: + - {social link 1 URL} + - {social link 2 URL} + + legal: + name: Ribose + tos_link: https://www.ribose.com/terms-of-service + privacy_policy_link: https://www.ribose.com/privacy-policy + + home_calls_to_action: + - { url: "/about", title: "About Prexian" } + + github_repo_url: https://github.com/riboseinc/prexian + +tag_namespaces: + software: + writtenin: "Written in" + bindingsfor: "Bindings for" + user: "Target user" + interface: "Interface" + specs: + audience: "Audience" + completion_status: "Status" + +# Jekyll configuration +# includes_dir: '_includes' + +# Must be set for Prexian +collections: + projects: + output: false + software: + output: true + permalink: /software/:path/ + specs: + output: true + permalink: /specs/:path/ + posts: + output: true + permalink: /blog/:year-:month-:day-:title/ + pages: + output: true + permalink: /:name/ + +# Must be set for Prexian +defaults: + # Theme defaults. + # MUST be duplicated from theme’s _config.yml + # (does not get inherited, unlike the collections hash) + - scope: + path: "" + values: + layout: default + - scope: + path: _posts + type: posts + values: + layout: post + - scope: + path: _software + type: software + values: + layout: product + - scope: + path: _specs + type: specs + values: + layout: spec + +# Development settings +# incremental: true +# livereload: true diff --git a/_config_test.yml b/_config_test.yml deleted file mode 100644 index e2239f2..0000000 --- a/_config_test.yml +++ /dev/null @@ -1,49 +0,0 @@ -markdown: kramdown - -plugins: - - jekyll-seo-tag - -includes_dir: _includes - -permalink: /blog/:month-:day-:year/:title/ - -exclude: - - /**/.git/* - - _posts - -collections: - projects: - output: false - software: - output: true - permalink: /software/:path/ - specs: - output: true - permalink: /specs/:path/ - posts: - output: true - permalink: /blog/:month-:day-:year/:title/ - pages: - output: true - permalink: /:name/ - -defaults: - - scope: - path: "" - values: - layout: default - - scope: - path: _posts - type: posts - values: - layout: post - - scope: - path: _software - type: software - values: - layout: product - - scope: - path: _specs - type: specs - values: - layout: spec diff --git a/_includes/custom-head.html b/_includes/custom-head.html new file mode 100644 index 0000000..5f3e217 --- /dev/null +++ b/_includes/custom-head.html @@ -0,0 +1,5 @@ +<!-- Placeholder to allow users to add more metadata to <head /> --> +<!-- For example, to add favicons: +1. Head over to https://realfavicongenerator.net/ to add your own favicons. +2. Customize this file in your source directory and insert the given code snippet. +--> diff --git a/_includes/custom-intro.html b/_includes/custom-intro.html new file mode 100644 index 0000000..636d25b --- /dev/null +++ b/_includes/custom-intro.html @@ -0,0 +1,5 @@ +<section class="summary"> + <p> + Ribose proudly announces availability of Prexian. + </p> +</section> diff --git a/_includes/featured_posts.html b/_includes/featured_posts.html index e1b38e3..95acd9d 100644 --- a/_includes/featured_posts.html +++ b/_includes/featured_posts.html @@ -1,10 +1,10 @@ -{% assign posts = site.posts_combined %} +{% assign posts = site.prexian.posts_combined %} <section class="featured-posts"> <h2 class="title">From the <a href="/blog/">Blog</a></h2> <div class="items"> - {% for item in posts | limit: site.max_featured_posts %} + {% for item in posts | limit: site.prexian.max_featured_posts %} {% include post-card.html post=item %} {% endfor %} </div> diff --git a/_includes/featured_software.html b/_includes/featured_software.html index 737814d..4e8c341 100644 --- a/_includes/featured_software.html +++ b/_includes/featured_software.html @@ -1,23 +1,23 @@ <section class="software"> - {% unless site.one_software %} + {% unless site.prexian.one_software %} <h2 class="title"> Software - {% if site.num_all_software > site.num_featured_software %} + {% if site.prexian.num_all_software > site.prexian.num_featured_software %} — <a href="/software/">see all</a> {% endif %} </h2> {% endunless %} <div class="items"> - {% assign software = site.featured_software %} - {% for item in software | limit: site.max_featured_software %} + {% assign software = site.prexian.featured_software %} + {% for item in software | limit: site.prexian.max_featured_software %} {% assign url = item.url %} - {% assign item_docs = site.software | where_exp: "item", "item.url contains url" %} - {% assign item_data = site.software | where_exp: "item", "item.url == url" | first %} + {% assign item_docs = site.prexian.software | where_exp: "item", "item.url contains url" %} + {% assign item_data = site.prexian.software | where_exp: "item", "item.url == url" | first %} {% assign nav = item_docs | where_exp: "item", "item.path contains 'docs/navigation'" | first %} <div class="item" role="article"> - {% unless site.one_software %} + {% unless site.prexian.one_software %} <header> <div class="logo-container"> {% include software-symbol.html item_id=item.id %} diff --git a/_includes/featured_specs.html b/_includes/featured_specs.html index 2316f56..dcadb1b 100644 --- a/_includes/featured_specs.html +++ b/_includes/featured_specs.html @@ -1,15 +1,15 @@ <section class="specs"> <h2 class="title"> - Specification{% if site.num_all_specs > 1 %}s{% endif %} - {% if site.num_all_specs > site.num_featured_specs %} + Specification{% if site.prexian.num_all_specs > 1 %}s{% endif %} + {% if site.prexian.num_all_specs > site.prexian.num_featured_specs %} — <a href="/specs/">see all</a> {% endif %} </h2> <div class="items"> - {% assign specs = site.featured_specs %} - {% for item in specs | limit: site.max_featured_specs %} + {% assign specs = site.prexian.featured_specs %} + {% for item in specs | limit: site.prexian.max_featured_specs %} {% assign url = item.url %} - {% assign item_data = site.specs | where_exp: "item", "item.url == url" | first %} + {% assign item_data = site.prexian.specs | where_exp: "item", "item.url == url" | first %} <div class="item" role="article"> <header> diff --git a/_includes/head.html b/_includes/head.html index 84db976..6d7399d 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -7,3 +7,4 @@ {% endfor %} {% endif %} {% seo %} +{%- include custom-head.html -%} diff --git a/_includes/home-hero.html b/_includes/home-hero.html index 4a7990e..3c1898b 100644 --- a/_includes/home-hero.html +++ b/_includes/home-hero.html @@ -1,21 +1,21 @@ <div class="text"> - <h1 class="title">{{ site.tagline }}</h1> + <h1 class="title">{{ site.prexian.tagline }}</h1> - {% if site.pitch %} - <p class="desc">{{ site.pitch | safe }}</p> + {% if site.prexian.pitch %} + <p class="desc">{{ site.prexian.pitch | safe }}</p> {% endif %} - {% if site.is_hub %} + {% if site.prexian.site_type == "hub" %} <div class="cta"> <a class="button" href="{{ "/projects/" | relative_url }}"> <i class="icon fas fa-search"></i> Explore Projects </a> </div> - {% else if site.one_software %} + {% else if site.prexian.one_software %} {% else %} <div class="cta"> - {% assign ctas = site.home_calls_to_action | slice: 0, 2 %} + {% assign ctas = site.prexian.home_calls_to_action | slice: 0, 2 %} {% for link in ctas %} <a class="button" href="{{ link.url | relative_url }}"> diff --git a/_includes/home-hub.html b/_includes/home-hub.html index 79e3354..11666ce 100644 --- a/_includes/home-hub.html +++ b/_includes/home-hub.html @@ -1,6 +1,6 @@ -{% assign projects = site.projects | where_exp: "item", "item.home_url != nil" %} -{% assign posts = site.posts_combined %} -{% assign num_posts = site.num_posts_combined %} +{% assign projects = site.prexian.projects | where_exp: "item", "item.home_url != nil" %} +{% assign posts = site.prexian.posts_combined %} +{% assign num_posts = site.prexian.num_posts_combined %} {% assign featured_projects = projects | where: "featured", true %} {% assign num_featured_projects = featured_projects | size %} @@ -14,8 +14,8 @@ <h2 class="title">Featured Projects</h2> <a class="item" href="{{ item.home_url }}" role="article"> <header> <div class="logo-container"> - {% assign symbol_path = item.path | split: "/" | slice: 1, 1 | join: "/" | append: "/assets/symbol.svg" %} - {% assign relative_symbol_path = "/projects/" | append: symbol_path %} + {% assign project_name = item.path | split: "/" | last | replace: ".md", "" | replace: ".adoc", "" %} + {% assign relative_symbol_path = "/assets/projects/" | append: project_name | append: "/symbol.svg" | relative_path %} <div class="logo"><img src="{{ relative_symbol_path }}"></div> </div> <h3 class="title">{{ item.title }}</h3> @@ -51,7 +51,8 @@ <h2 class="title">From the Blog</h2> {% assign num_other_projects = other_projects | size %} {% if num_other_projects > 0 %} <section class="other-projects"> - {% include assets/symbol.svg %} + {% assign relative_symbol_path = "/assets/img/symbol.svg" | relative_path %} + <img src="{{ relative_symbol_path }}"/> <h2 class="title">Other Projects</h2> @@ -60,8 +61,8 @@ <h2 class="title">Other Projects</h2> <a class="item" href="{{ item.home_url }}" role="article"> <header> <div class="logo-container"> - {% assign symbol_path = item.path | split: "/" | slice: 1, 1 | join: "/" | append: "/assets/symbol.svg" %} - {% assign relative_symbol_path = "/projects/" | append: symbol_path %} + {% assign project_name = item.path | split: "/" | last | replace: ".md", "" | replace: ".adoc", "" %} + {% assign relative_symbol_path = "/assets/projects/" | append: project_name | append: "/symbol.svg" | relative_path %} <div class="logo"><img src="{{ relative_symbol_path }}"></div> </div> <h3 class="title">{{ item.title }}</h3> diff --git a/_includes/home-project.html b/_includes/home-project.html index 7653729..cb94e9f 100644 --- a/_includes/home-project.html +++ b/_includes/home-project.html @@ -1,10 +1,10 @@ -{% assign num_featured_software = site.featured_software | size %} -{% assign num_featured_specs = site.featured_specs | size %} -{% assign num_posts = site.num_posts_combined %} +{% assign num_featured_software = site.prexian.featured_software | size %} +{% assign num_featured_specs = site.prexian.featured_specs | size %} +{% assign num_posts = site.prexian.num_posts_combined %} {% assign num_posts_specs = num_posts | plus: num_featured_specs %} -{% for section_name in site.landing_priority %} +{% for section_name in site.prexian.landing_priority %} {% if section_name == "specs" and num_featured_specs > 0 %} {% include featured_specs.html %} {% elsif section_name == "software" and num_featured_software > 0 %} diff --git a/_includes/index-page-item-filter.html b/_includes/index-page-item-filter.html index b428b9e..11f59dd 100644 --- a/_includes/index-page-item-filter.html +++ b/_includes/index-page-item-filter.html @@ -36,7 +36,7 @@ </ul> {% comment %}Namespaced tags{% endcomment %} - {% for namespace in site.tag_namespaces[include.tag_namespaces] %} + {% for namespace in site.prexian.tag_namespaces[include.tag_namespaces] %} {% assign namespace_human = namespace[1] %} {% assign namespace_id = namespace[0] %} @@ -71,7 +71,7 @@ {% if include.tag %} {% assign tag_human = include.tag | split: ":" | last | replace: "_", " " %} {% if tag_namespace != "" %} - {% assign namespace = site.tag_namespaces[include.tag_namespaces][tag_namespace] %} + {% assign namespace = site.prexian.tag_namespaces[include.tag_namespaces][tag_namespace] %} {% endif %} <header class="filter-header"> <h3 class="title"> diff --git a/_includes/item-doc-page.html b/_includes/item-doc-page.html index 8a6326d..dbd3a60 100644 --- a/_includes/item-doc-page.html +++ b/_includes/item-doc-page.html @@ -29,7 +29,7 @@ {% if is_docs_landing != true %} <header class="documentation-header {% if num_top_nav_items > 0 %} has-nav {% endif %}"> <div class="nav-header"> - {% if site.one_software %} + {% if site.prexian.one_software %} <h3 class="title"> Docs </h3> @@ -103,7 +103,7 @@ <h1 class="text">{{ page.article_header_title | default: page.title }}</h1> {% assign suggest_edits_base_url = item_data.docs.git_repo_url | default: item_data.repo_url %} {% if suggest_edits_base_url %} - <a href="{{ suggest_edits_base_url }}/edit/{{ item_data.docs.git_repo_branch | default: site.default_repo_branch | default: 'main' }} /{{ item_data.docs.git_repo_subtree | default: "docs" }}/{{ page.path | split: "/" | last }}" class="docs-suggest-edits">Suggest edits to this page</a> + <a href="{{ suggest_edits_base_url }}/edit/{{ item_data.docs.git_repo_branch | default: site.prexian.default_repo_branch | default: 'main' }} /{{ item_data.docs.git_repo_subtree | default: "docs" }}/{{ page.path | split: "/" | last }}" class="docs-suggest-edits">Suggest edits to this page</a> {% endif %} </nav> {% endif %} diff --git a/_includes/legal.html b/_includes/legal.html index bb3828b..b0b97ed 100644 --- a/_includes/legal.html +++ b/_includes/legal.html @@ -1,24 +1,24 @@ <span class="copyright"> - <span class="copyright-head">Copyright © {{ site.time | date: '%Y' }} {{ site.legal.name }} —</span> + <span class="copyright-head">Copyright © {{ site.time | date: '%Y' }} {{ site.prexian.legal.name }} —</span> <span class="copyright-tail">All rights reserved.</span> </span> -{% if site.legal.privacy_policy_link or site.legal.tos_link %} +{% if site.prexian.legal.privacy_policy_link or site.prexian.legal.tos_link %} <nav aria-label="legal links"> - {% if site.legal.tos_link %} - <a href="{{ site.legal.tos_link }}">Terms of Service</a> + {% if site.prexian.legal.tos_link %} + <a href="{{ site.prexian.legal.tos_link }}">Terms of Service</a> {% endif %} - {% if site.legal.privacy_policy_link %} - <a href="{{ site.legal.privacy_policy_link }}">Privacy Policy</a> + {% if site.prexian.legal.privacy_policy_link %} + <a href="{{ site.prexian.legal.privacy_policy_link }}">Privacy Policy</a> {% endif %} - {% if site.legal.security_advisories_link %} - <a href="{{ site.legal.security_advisories_link }}">Advisories</a> + {% if site.prexian.legal.security_advisories_link %} + <a href="{{ site.prexian.legal.security_advisories_link }}">Advisories</a> {% endif %} - {% if site.legal.disclosure_policy_link %} - <a href="{{ site.legal.disclosure_policy_link }}">Disclosure policy</a> + {% if site.prexian.legal.disclosure_policy_link %} + <a href="{{ site.prexian.legal.disclosure_policy_link }}">Disclosure policy</a> {% endif %} - {% if site.legal.security_feedback_link %} - <a href="{{ site.legal.security_feedback_link }}">Security feedback</a> + {% if site.prexian.legal.security_feedback_link %} + <a href="{{ site.prexian.legal.security_feedback_link }}">Security feedback</a> {% endif %} </nav> {% endif %} diff --git a/_includes/logo.html b/_includes/logo.html index 189ecfe..0d8b81b 100644 --- a/_includes/logo.html +++ b/_includes/logo.html @@ -1 +1,4 @@ -<span aria-label="project logo" class="symbol">{% include assets/symbol.svg %}</span> <span class="title">{% include title.html %}</span> +<span aria-label="project logo" class="symbol"> + {% assign relative_symbol_path = "/assets/img/symbol.svg" | relative_path %} + <img src="{{ relative_symbol_path }}"/> +</span> <span class="title">{% include title.html %}</span> diff --git a/_includes/nav-links.html b/_includes/nav-links.html index 5b2fd3e..5fd4af7 100644 --- a/_includes/nav-links.html +++ b/_includes/nav-links.html @@ -1,45 +1,45 @@ -{% assign contact_link = "mailto:" | append: site.contact_email %} +{% assign contact_link = "mailto:" | append: site.prexian.contact_email %} -{% if site.algolia_search %} +{% if site.prexian.algolia_search %} <div class="search-widget"> <input type="search" id="siteSearchInput" placeholder="Type to search…"> </div> {% endif %} -{% if site.is_hub %} +{% if site.prexian.site_type == "hub" %} {% include nav-page-link.html htmlclass="projects" url="/projects/" title="Projects" %} - {% if site.num_all_software > 0 %} + {% if site.prexian.num_all_software > 0 %} {% include nav-page-link.html htmlclass="software" url="/software/" title="Software" active_for_nested=true %} {% endif %} - {% if site.num_all_specs > 0 %} + {% if site.prexian.num_all_specs > 0 %} {% include nav-page-link.html htmlclass="specs" url="/specs/" title="Specifications" active_for_nested=true %} {% endif %} - {% if site.num_posts_combined > 0 %} + {% if site.prexian.num_posts_combined > 0 %} {% include nav-page-link.html htmlclass="blog" url="/blog/" title="Blog" active_for_nested=true %} {% endif %} {% else %} - {% if site.one_software %} - {% include nav-page-link.html htmlclass="docs" url=site.one_software.url title="Docs" active_for_nested=true %} + {% if site.prexian.one_software %} + {% include nav-page-link.html htmlclass="docs" url=site.prexian.one_software.url title="Docs" active_for_nested=true %} {% else %} - {% if site.num_all_software > 0 %} + {% if site.prexian.num_all_software > 0 %} {% include nav-page-link.html htmlclass="software" url="/software/" title="Software" active_for_nested=true %} {% endif %} {% endif %} - {% if site.num_all_specs > 0 %} + {% if site.prexian.num_all_specs > 0 %} {% include nav-page-link.html htmlclass="specs" url="/specs/" title="Specifications" active_for_nested=true %} {% endif %} {% include project-nav.html %} - {% if site.num_posts_combined > 0 %} + {% if site.prexian.num_posts_combined > 0 %} {% include nav-page-link.html htmlclass="blog" url="/blog/" title="Blog" active_for_nested=true %} {% endif %} {% endif %} -{% if site.algolia_search %} +{% if site.prexian.algolia_search %} <a href="javascript: void 0;" class="search"><i class="fas fa-search"></i></a> {% endif %} diff --git a/_includes/post-card.html b/_includes/post-card.html index 6aac772..3b7aad6 100644 --- a/_includes/post-card.html +++ b/_includes/post-card.html @@ -25,15 +25,16 @@ {% endif %} <div class="card-body"> - {% if include.post.parent_project == nil and site.is_hub == true %} + {% if include.post.parent_project == nil and site.prexian.site_type == "hub" %} <div class="hub-symbol"> - {% include assets/symbol.svg %} + {% assign relative_symbol_path = "/assets/img/symbol.svg" | relative_path %} + <img src="{{ relative_symbol_path }}"/> </div> {% endif %} <header> {% if include.post.parent_project %} - {% assign project_symbol_path = include.post.parent_project.name | append: "/assets/symbol.svg" %} + {% assign project_symbol_path = include.post.parent_project.name | append: "/assets/img/symbol.svg" | relative_path %} {% assign relative_symbol_path = "/projects/" | append: project_symbol_path %} <div class="parent-project"> <div class="project-logo"> diff --git a/_includes/social-links.html b/_includes/social-links.html index 3f81618..a31dad5 100644 --- a/_includes/social-links.html +++ b/_includes/social-links.html @@ -1,6 +1,6 @@ -{% if site.social.links %} +{% if site.prexian.social.links %} <aside class="social-links"> - {% for link in site.social.links %} + {% for link in site.prexian.social.links %} {% if link contains "twitter.com" %} <a href="{{ link }}" aria-label="Twitter link"> <i class="fab fa-twitter"></i> diff --git a/_includes/software-card-hub.html b/_includes/software-card-hub.html index f7cd5c2..af04b94 100644 --- a/_includes/software-card-hub.html +++ b/_includes/software-card-hub.html @@ -11,7 +11,7 @@ <a class="item {% if include.item.feature_with_priority != nil %}featured-item{% endif %}" href="{{ product_home_url }}" role="article"> - {% if include.item_type == 'software' and site.is_hub != true %} + {% if include.item_type == 'software' and site.prexian.site_type == "project" %} <div class="logo-container"> {% include software-symbol.html item_id=include.item.id %} </div> @@ -19,7 +19,7 @@ <header> {% if include.item.project_data != nil and include.item.project_name != nil %} - {% assign project_symbol_path = include.item.project_name | append: "/assets/symbol.svg" %} + {% assign project_symbol_path = include.item.project_name | append: "/assets/img/symbol.svg" | relative_path %} {% assign relative_symbol_path = "/projects/" | append: project_symbol_path %} <div class="parent-project"> <div class="project-logo"><img src="{{ relative_symbol_path }}"></div> diff --git a/_includes/software-symbol.html b/_includes/software-symbol.html index e46da48..ae036ca 100644 --- a/_includes/software-symbol.html +++ b/_includes/software-symbol.html @@ -1,4 +1,4 @@ -{% assign symbol_path = include.item_id | split: "/" | slice: 2, 1 | join: "/" | append: "/assets/symbol.svg" %} +{% assign symbol_path = include.item_id | split: "/" | slice: 2, 1 | join: "/" | append: "/assets/img/symbol.svg" | relative_path %} {% assign relative_symbol_path = "/software/" | append: symbol_path %} <div class="logo"> diff --git a/_includes/tag-list.html b/_includes/tag-list.html index 4d1862d..ec4b3af 100644 --- a/_includes/tag-list.html +++ b/_includes/tag-list.html @@ -4,7 +4,7 @@ {% for tag in include.tags %} {% if tag contains ":" %} {% assign namespace_id = tag | split: ":" | first %} - {% assign namespace = site.tag_namespaces[include.item_type][namespace_id] %} + {% assign namespace = site.prexian.tag_namespaces[include.item_type][namespace_id] %} {% assign tag_human = tag | split: ":" | last | replace: "_", " " %} {% else %} {% assign tag_human = tag | replace: "_", " " %} diff --git a/_includes/title.html b/_includes/title.html index f6d5669..4d5c1b2 100644 --- a/_includes/title.html +++ b/_includes/title.html @@ -1 +1 @@ -{{ site.title }} \ No newline at end of file +{{ site.prexian.title }} \ No newline at end of file diff --git a/_layouts/blog-index.html b/_layouts/blog-index.html index 3bffc08..e5c345a 100644 --- a/_layouts/blog-index.html +++ b/_layouts/blog-index.html @@ -3,17 +3,17 @@ --- <section class="items"> - {% if site.is_hub %} - {% include assets/symbol.svg %} + {% if site.prexian.site_type == "hub" %} + {% assign relative_symbol_path = "/hub/assets/img/symbol.svg" | relative_path %} + <img src="{{ relative_symbol_path }}"/> {% endif %} - {% for item in site.posts_combined %} + {% for item in site.prexian.posts_combined %} {% if forloop.first %} {% assign can_be_featured = true %} {% else %} {% assign can_be_featured = false %} {% endif %} - {% include post-card.html post=item can_be_featured=can_be_featured %} {% endfor %} </section> diff --git a/_layouts/default.html b/_layouts/default.html index 1236fde..71e7622 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -20,15 +20,15 @@ <link id="themeCSS" rel="stylesheet" href="{{ "assets/css/style.css" | relative_url }}"> - {% unless site.no_auto_fontawesome %} + {% unless site.prexian.no_auto_fontawesome %} <script defer - src="https://use.fontawesome.com/releases/{{ site.fontawesome_cdn.version }}/js/all.js" - integrity="{{ site.fontawesome_cdn.integrity }}" + src="https://use.fontawesome.com/releases/{{ site.prexian.fontawesome_cdn.version }}/js/all.js" + integrity="{{ site.prexian.fontawesome_cdn.integrity }}" crossorigin="anonymous"></script> {% endunless %} - {% if site.algolia_search %} + {% if site.prexian.algolia_search %} <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"> {% endif %} @@ -49,14 +49,14 @@ {% include head.html %} </head> -{% assign num_projects = site.projects | size %} +{% assign num_projects = site.prexian.projects | size %} <body class=" {{ page.html-class }} {{ layout.html-class }} {% if num_projects > 0 %} site--hub {% else %} site--project - {% if site.one_software %}site--project--one-software{% endif %} + {% if site.prexian.one_software %}site--project--one-software{% endif %} {% endif %} {% if page.layout %}layout--{{ page.layout }}{% endif %}"> <div class="underlay header"> @@ -93,30 +93,50 @@ <h1 class="site-logo"><a href="/">{% include logo.html %}</a></h1> <div class="underlay footer"> <footer> - {% if site.num_all_specs > 0 or site.num_all_software > 0 or site.extra_footer_links %} + {% if site.prexian.num_all_specs > 0 or site.prexian.num_all_software > 0 or site.prexian.extra_footer_links %} <nav class="links" aria-label="site links"> - {% for link in site.extra_footer_links %} + {% for link in site.prexian.extra_footer_links %} <a href="{{ link.url }}">{{ link.title }}</a> {% endfor %} - {% if site.num_all_software > 0 %} - <a href="/software/">All {{ site.title }} software</a> + {% if site.prexian.num_all_software > 0 %} + <a href="/software/">All {{ site.prexian.title }} software</a> {% endif %} - {% if site.num_all_specs > 0 %} + {% if site.prexian.num_all_specs > 0 %} <a href="/specs/">All specifications</a> {% endif %} </nav> {% endif %} - {% if site.is_hub %} + {% assign config = site.prexian %} + {% if config.site_type == 'hub' %} <div class="site-logo" aria-label="logo">{% include logo.html %}</div> {% else %} - <div class="parent-hub-plug"> - <span class="label">{{ site.title }} is</span> - <a class="logo" role="presentation" href="{{ site.parent_hub.home_url }}" - aria-label="parent project logo"> - {% include parent-hub/assets/symbol.svg %} <span class="title">{% include parent-hub/title.html %}</span> + {% if config.hub.home_url %} + <div class="hub-plug"> + <span class="label">{{ site.prexian.title }} is</span> + <a class="logo" role="presentation" href="{{ site.prexian.hub.home_url }}" + aria-label="hub project logo"> + + {% capture hub_title %}{% include hub/title.html %}{% endcapture %} + {% unless hub_title contains "Liquid error" %} + {% assign title_exists = true %} + {% endunless %} + + {% assign relative_symbol_path = "/hub/assets/img/symbol.svg" | relative_path %} + <img src="{{ relative_symbol_path }}"/> + +  <span class="title"> + {% if title_exists %} + {{ hub_title }} + {% else %} + {{ site.data.hub.title }} + {% endif %} + </span> </a> </div> + {% else %} + <div class="site-logo" aria-label="logo">{% include logo.html %}</div> + {% endif %} {% endif %} <div class="legal">{% include legal.html %}</div> @@ -125,7 +145,7 @@ <h1 class="site-logo"><a href="/">{% include logo.html %}</a></h1> </footer> </div> - {% if site.algolia_search %} + {% if site.prexian.algolia_search %} <script src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script> {% comment %} @@ -137,8 +157,8 @@ <h1 class="site-logo"><a href="/">{% include logo.html %}</a></h1> window.initAlgolia = function () { if (window.docsearch) { docsearch({ - apiKey: '{{ site.algolia_search.api_key }}', - indexName: '{{ site.algolia_search.index_name }}', + apiKey: '{{ site.prexian.algolia_search.api_key }}', + indexName: '{{ site.prexian.algolia_search.index_name }}', inputSelector: 'input[type=search]', debug: false, }); diff --git a/_layouts/docs-base.html b/_layouts/docs-base.html index 0236f71..2a4d091 100644 --- a/_layouts/docs-base.html +++ b/_layouts/docs-base.html @@ -47,14 +47,14 @@ <h3 class="title"> <h1 class="text">{{ prominent_title | default: title }}</h1> </div> - {% if site.github_repo_url or num_top_nav_items > 0 %} + {% if site.prexian.github_repo_url or num_top_nav_items > 0 %} <nav role="toolbar"> {% if num_top_nav_items > 0 %} <button class="docs-nav-toggle">Toggle table of contents</button> {% endif %} - {% if site.github_repo_url %} - <a href="{{ site.github_repo_url }}/edit/{{ item_data.docs.github_repo_branch | default: site.default_repo_branch | default: 'main' }}/{{ page.path }}" class="docs-suggest-edits">Suggest edits to this page</a> + {% if site.prexian.github_repo_url %} + <a href="{{ site.prexian.github_repo_url }}/edit/{{ item_data.docs.github_repo_branch | default: site.prexian.default_repo_branch | default: 'main' }}/{{ page.path }}" class="docs-suggest-edits">Suggest edits to this page</a> {% endif %} </nav> {% endif %} diff --git a/_layouts/home.html b/_layouts/home.html index a906d03..c6db714 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -4,7 +4,7 @@ {{ content }} -{% assign num_projects = site.projects | size %} +{% assign num_projects = site.prexian.projects | size %} {% if num_projects > 0 %} {% include home-hub.html %} diff --git a/_layouts/product.html b/_layouts/product.html index b454b6b..21a71a2 100644 --- a/_layouts/product.html +++ b/_layouts/product.html @@ -3,7 +3,7 @@ html-class: docs-page --- -{% include item-doc-page.html items=site.software item_type='software' %} +{% include item-doc-page.html items=site.prexian.software item_type='software' %} <script src="{{ "/assets/js/anchor-scroll.js" | relative_url }}"></script> <script src="{{ "/assets/js/adoc-toc.js" | relative_url }}"></script> diff --git a/_layouts/project-index.html b/_layouts/project-index.html index 389e936..cdcb4e8 100644 --- a/_layouts/project-index.html +++ b/_layouts/project-index.html @@ -1,12 +1,13 @@ --- layout: default --- -{% assign projects = site.projects | where_exp: "item", "item.home_url != nil" %} +{% assign projects = site.prexian.projects | where_exp: "item", "item.home_url != nil" %} {% assign num_projects = projects | size %} {% if num_projects > 0 %} <section class="items"> - {% include assets/symbol.svg %} + {% assign relative_symbol_path = "/hub/assets/img/symbol.svg" | relative_path %} + <img src="{{ relative_symbol_path }}"/> {% for project in projects %} <a class="item {% if project.featured == true %}featured{% endif %}" @@ -15,7 +16,7 @@ <header> <div class="logo-container"> - {% assign symbol_path = project.path | split: "/" | slice: 1, 1 | join: "/" | append: "/assets/symbol.svg" %} + {% assign symbol_path = project.path | split: "/" | slice: 1, 1 | join: "/" | append: "/assets/img/symbol.svg" | relative_path %} {% assign relative_symbol_path = "/projects/" | append: symbol_path %} <div class="logo"><img src="{{ relative_symbol_path }}"></div> </div> diff --git a/_layouts/software-index.html b/_layouts/software-index.html index 5089485..e164911 100644 --- a/_layouts/software-index.html +++ b/_layouts/software-index.html @@ -2,29 +2,30 @@ layout: default --- -{% include index-page-item-filter.html url_tag_prefix="/software/" tag_namespaces="software" items=site.all_software tag=page.tag %} +{% include index-page-item-filter.html url_tag_prefix="/software/" tag_namespaces="software" items=site.prexian.all_software tag=page.tag %} <section class="items"> - {% if site.is_hub %} - {% include assets/symbol.svg %} + {% if site.prexian.site_type == "hub" %} + {% assign symbol_path = "/assets/img/symbol.svg" | relative_path %} + <img {% endif %} {% if page.tag and page.items %} {% for item in page.items %} {% include software-card-hub.html item=item item_type='software' %} {% endfor %} - {% elsif site.is_hub %} - {% for item in site.all_software %} + {% elsif site.prexian.site_type == "hub" %} + {% for item in site.prexian.all_software %} {% include software-card-hub.html item=item item_type='software' %} {% endfor %} {% else %} - {% if site.num_featured_software > 0 %} - {% for item in site.featured_software %} + {% if site.prexian.num_featured_software > 0 %} + {% for item in site.prexian.featured_software %} {% include software-card-hub.html item=item item_type='software' %} {% endfor %} <hr> {% endif %} - {% for item in site.non_featured_software %} + {% for item in site.prexian.non_featured_software %} {% include software-card-hub.html item=item item_type='software' %} {% endfor %} {% endif %} diff --git a/_layouts/spec-index.html b/_layouts/spec-index.html index 134011f..1d802c8 100644 --- a/_layouts/spec-index.html +++ b/_layouts/spec-index.html @@ -2,29 +2,30 @@ layout: default --- -{% include index-page-item-filter.html url_tag_prefix="/specs/" tag_namespaces="specs" items=site.all_specs tag=page.tag %} +{% include index-page-item-filter.html url_tag_prefix="/specs/" tag_namespaces="specs" items=site.prexian.all_specs tag=page.tag %} <section class="items"> - {% if site.is_hub %} - {% include assets/symbol.svg %} + {% if site.prexian.site_type == "hub" %} + {% assign relative_symbol_path = "/hub/assets/img/symbol.svg" | relative_path %} + <img src="{{ relative_symbol_path }}"/> {% endif %} {% if page.tag and page.items %} {% for item in page.items %} {% include software-card-hub.html item=item item_type='specs' %} {% endfor %} - {% elsif site.is_hub %} - {% for item in site.all_specs %} + {% elsif site.prexian.site_type == "hub" %} + {% for item in site.prexian.all_specs %} {% include software-card-hub.html item=item item_type='specs' %} {% endfor %} {% else %} - {% if site.num_featured_specs > 0 %} - {% for item in site.featured_specs %} + {% if site.prexian.num_featured_specs > 0 %} + {% for item in site.prexian.featured_specs %} {% include software-card-hub.html item=item item_type='specs' %} {% endfor %} <hr> {% endif %} - {% for item in site.non_featured_specs %} + {% for item in site.prexian.non_featured_specs %} {% include software-card-hub.html item=item item_type='specs' %} {% endfor %} {% endif %} diff --git a/_sass/jekyll-theme-open-project.scss b/_sass/jekyll-theme-open-project.scss index fde7524..361c07d 100644 --- a/_sass/jekyll-theme-open-project.scss +++ b/_sass/jekyll-theme-open-project.scss @@ -1,2 +1,2 @@ // Compatibility file for old name -@import "jekyll-theme-rop"; +@import "prexian"; diff --git a/_sass/rop-base.scss b/_sass/prexian-base.scss similarity index 97% rename from _sass/rop-base.scss rename to _sass/prexian-base.scss index 41933fe..8f90e70 100644 --- a/_sass/rop-base.scss +++ b/_sass/prexian-base.scss @@ -1,4 +1,5 @@ -@import 'rop-mixins'; +@use "sass:color"; +@import 'prexian-mixins'; $easeOutCirc: cubic-bezier(0.075, 0.82, 0.165, 1); @@ -249,8 +250,10 @@ a { white-space: nowrap; + color: $primary-color; + .namespace { - color: lighten($main-font-color, 50%); + color: color.adjust($main-font-color, $lightness: 50%); font-weight: normal; } @@ -265,8 +268,6 @@ a { margin: 0; } - color: $primary-color; - .site--hub.layout--software-index & { color: $hub-software--primary-color; } @@ -364,7 +365,7 @@ a { height: 28px; svg path { - fill: lighten(desaturate($primary-color, 30), 45); + fill: color.adjust(color.adjust($primary-color, $saturation: -30%), $lightness: 45%); } } header { @@ -479,7 +480,7 @@ a { } table { - $border-color: lighten($main-font-color, 80); + $border-color: color.adjust($main-font-color, $lightness: 80%); border-collapse: collapse; width: 100%; diff --git a/_sass/rop-header-footer.scss b/_sass/prexian-header-footer.scss similarity index 97% rename from _sass/rop-header-footer.scss rename to _sass/prexian-header-footer.scss index 5ad5002..522f4a9 100644 --- a/_sass/rop-header-footer.scss +++ b/_sass/prexian-header-footer.scss @@ -112,7 +112,7 @@ body > .underlay > footer { } } } - + .site--hub.layout--home > & { > .hero { text-align: left; @@ -285,8 +285,8 @@ body > .underlay > header { } body > .underlay > footer { - padding-top: 50px; - padding-bottom: 50px; + padding-top: 30px; + padding-bottom: 20px; align-items: flex-start; flex-flow: column nowrap; @@ -297,9 +297,14 @@ body > .underlay > footer { color: white; } - .parent-hub-plug { + .hub-plug { .logo { display: block; + + svg { + height: 30px; + vertical-align: middle; + } } display: flex; diff --git a/_sass/rop-mixins.scss b/_sass/prexian-mixins.scss similarity index 98% rename from _sass/rop-mixins.scss rename to _sass/prexian-mixins.scss index 200c06a..927a1b5 100644 --- a/_sass/rop-mixins.scss +++ b/_sass/prexian-mixins.scss @@ -1,13 +1,15 @@ +@use "sass:color"; + @mixin tbd($color: red) { position: relative; padding: 0 .3em; - border: 1px dashed lighten($main-font-color, 70); + border: 1px dashed color.adjust($main-font-color, $lightness: 70%); &:after { position: absolute; left: 100%; content: "TBD"; - color: lighten($main-font-color, 70); + color: color.adjust($main-font-color, $lightness: 70%); font-size: .6em; line-height: 1; padding: .05em .4em; @@ -71,7 +73,7 @@ } @mixin code-snippet() { - color: lighten($main-font-color, 24); + color: color.adjust($main-font-color, $lightness: 24%); @include padded-code-snippet(); margin-top: -1px; @@ -94,14 +96,6 @@ margin-right: 10px; transition: box-shadow .2s ease-out; - .icon { - margin-right: 10px; - font-weight: 400; - position: relative; - top: 2px; - font-size: 120%; - } - &, &:link, &:visited { color: $color; background-color: $bgcolor; @@ -110,6 +104,14 @@ &:hover { box-shadow: 0 0 0 4px rgba(black, 0.2); } + + .icon { + margin-right: 10px; + font-weight: 400; + position: relative; + top: 2px; + font-size: 120%; + } } @mixin cta-button-mini($bgcolor, $color) { @@ -554,14 +556,13 @@ > section.documentation { flex: 1; + overflow-x: auto; // on narrow screens, code snippets & tables may overflow width @media screen and (min-width: $bigscreen-breakpoint) { display: flex; flex-flow: column nowrap; } - overflow-x: auto; // on narrow screens, code snippets & tables may overflow width - .docs-nav { $navFlexShare: 20%; @@ -586,12 +587,7 @@ right: 0; left: 0; - @media screen and (min-width: $bigscreen-breakpoint) { - left: unset; - } - padding: .75em $sidePadding; - z-index: 5; background: #f7f7f7; box-shadow: rgba(black, 0.7) -30px 0px 60px -60px; @@ -604,6 +600,10 @@ opacity .6s $easeOutCirc, box-shadow .6s $easeOutCirc; + @media screen and (min-width: $bigscreen-breakpoint) { + left: unset; + } + @media screen and (min-width: $bigscreen-breakpoint) { max-width: calc(#{$navFlexShare} - #{$sidePadding}); } @@ -727,13 +727,13 @@ margin-bottom: 4em; } > .title { + padding-top: 70px; + @media screen and (min-width: $bigscreen-breakpoint) { display: flex; flex-flow: row nowrap; } - padding-top: 70px; - > .logo-container { margin-right: 22px; diff --git a/_sass/jekyll-theme-rop.scss b/_sass/prexian.scss similarity index 96% rename from _sass/jekyll-theme-rop.scss rename to _sass/prexian.scss index a469e75..79278f7 100644 --- a/_sass/jekyll-theme-rop.scss +++ b/_sass/prexian.scss @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css?family=Inconsolata'); +@use "sass:color"; $font-family: Helvetica, Arial, sans-serif !default; $font-family-source: Inconsolata, monospace; @@ -6,7 +6,7 @@ $font-family-source: Inconsolata, monospace; $main-font-color: #000; $code-listing-background-color: rgba($main-font-color, 0.03); -$code-listing-border-color: lighten($main-font-color, 70); +$code-listing-border-color: color.adjust($main-font-color, $lightness: 70%); $primary-color: lightblue !default; $primary-dark-color: $primary-color !default; @@ -37,12 +37,11 @@ $bigscreen-breakpoint: 800px + $gutter * 2; $external-links-side-margin: 16px; // for external links - -@import "rop-base"; -@import "rop-header-footer"; +@import url('https://fonts.googleapis.com/css?family=Inconsolata'); +@import "prexian-base"; +@import "prexian-header-footer"; @import "headroom"; - main { /* Generic */ @@ -63,8 +62,10 @@ main { @extend .post-card; @include hoverable-card(4px, 12px, rgba(0, 0, 0, 0.08)); - padding: 32px 30px; - box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08); + & { + padding: 32px 30px; + text-align: center; + } .header { font-size: 22px; @@ -356,13 +357,13 @@ main { @extend .item-card; @include hoverable-card(4px, 16px, rgba(0, 0, 0, 0.12)); - padding: 40px 32px; - text-align: center; - - display: flex; - flex-flow: column nowrap; - - box-sizing: border-box; + & { + padding: 40px 32px; + text-align: center; + display: flex; + flex-flow: column nowrap; + box-sizing: border-box; + } @media screen and (min-width: $bigscreen-breakpoint) { flex-basis: calc( 100%/#{$featured-cols} - #{$gutter} ); @@ -483,10 +484,12 @@ main { .item { @extend .item-card; - @include hoverable-card(2px, 10px, rgba(desaturate($primary-color, 50), 0.08)); + @include hoverable-card(2px, 10px, rgba(color.adjust($primary-color, $saturation: -50%), 0.08)); - padding: 32px 30px; - text-align: center; + & { + padding: 32px 30px; + text-align: center; + } .logo { display: inline-block; @@ -572,8 +575,8 @@ body.site--project { margin-top: 30px; margin-bottom: 30px; .button { - @include cta-button($primary-color, white); border: 1px solid $accent-color; + @include cta-button($primary-color, white); } } p:first-child:first-letter { @@ -670,7 +673,7 @@ body.site--project { &.layout--spec-index > main { > .items .item { @extend .item-card; - @include hoverable-card(2px, 10px, rgba(desaturate($primary-color, 50), 0.08)); + @include hoverable-card(2px, 10px, rgba(color.adjust($primary-color, $saturation: -50%), 0.08)); padding: 30px 32px; flex: unset; @@ -744,7 +747,7 @@ body.site--project { font-size: 110%; font-weight: normal; align-self: flex-start; - color: lighten($main-font-color, 20); + color: color.adjust($main-font-color, $lightness: 20%); margin-bottom: $gutter; a { diff --git a/_sass/prexian/custom-styles.scss b/_sass/prexian/custom-styles.scss new file mode 100644 index 0000000..a8d446d --- /dev/null +++ b/_sass/prexian/custom-styles.scss @@ -0,0 +1,8 @@ +// Placeholder to allow overriding styles defined in the theme. +// This file is loaded after the base styles, so you can override +// any styles defined in the theme. + +// Default site logo styling +.site-logo svg path { + fill: white; +} diff --git a/_sass/prexian/custom-variables.scss b/_sass/prexian/custom-variables.scss new file mode 100644 index 0000000..8c58aa0 --- /dev/null +++ b/_sass/prexian/custom-variables.scss @@ -0,0 +1,8 @@ +// Placeholder to allow overriding variable defaults and mixins. +// This file is loaded before the base styles, so you can override +// any variables defined in the theme. + +// Default theme variables (can be overridden) +$primary-color: #05C7DF !default; +$accent-color: #FF6357 !default; +$main-background: linear-gradient(135deg, #0446CC 0%, #2864DD 100%) !default; diff --git a/assets/css/style.scss b/assets/css/style.scss index b3a47da..f6da55b 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -3,11 +3,7 @@ $primary-color: #05C7DF; $accent-color: #FF6357; - $main-background: linear-gradient(135deg, #0446CC 0%, #2864DD 100%); -@import 'jekyll-theme-rop'; - -.site-logo svg path { - fill: white; -} +@import "prexian"; +@import "prexian/custom-styles"; diff --git a/assets/symbol.svg b/assets/symbol.svg new file mode 100644 index 0000000..34325b0 --- /dev/null +++ b/assets/symbol.svg @@ -0,0 +1,19 @@ +<svg viewBox="0 0 85 71" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g class="Canvas" fill="none"> +<g class="fake logo"> +<g class="Shape"> +<path d="M 5.18494 26L 12 20.4972L 18.8151 26L 24 20.7672L 13.2167 0.676004C 12.733 -0.225335 11.267 -0.225335 10.7833 0.676004L 0 20.7672L 5.18494 26Z" transform="translate(30 0)" fill="#111111"/> +</g> +<g class="Shape_2"> +<path d="M 75.83 45.8686L 51.6053 0L 45.1415 6.62958L 38 0.770372L 30.8585 6.62958L 24.3947 0L 0.16995 45.8686C -0.0664507 46.3169 -0.0554589 46.8582 0.198126 47.2952C 0.452387 47.7322 0.911439 48 1.4076 48L 74.5924 48C 75.0886 48 75.5476 47.7322 75.8019 47.2952C 76.0555 46.8582 76.0665 46.3169 75.83 45.8686Z" transform="translate(4 23)" fill="#111111"/> +</g> +<g class="Shape_3"> +<path d="M 30 12.3799L 22.2626 0.635075C 21.9928 0.226511 21.5122 -0.0257198 21.0508 0.0020861C 20.5653 0.0166721 20.1225 0.283489 19.878 0.707351L 0.191654 34.8599C -0.0623847 35.3004 -0.0637627 35.8438 0.186845 36.2857C 0.437453 36.7276 0.90298 37 1.40626 37L 16.822 37L 30 12.3799Z" transform="translate(0 3)" fill="#905DEE"/> +</g> +<g class="Shape_4"> +<path d="M 29.8083 34.8598L 10.1218 0.705378C 9.87741 0.281508 9.43454 0.0146767 8.94912 7.57049e-05C 8.48703 -0.0047913 8.0064 0.22521 7.73724 0.633099L 0 12.3789L 13.1779 37L 28.5937 37C 29.097 37 29.5625 36.7276 29.8131 36.2857C 30.0638 35.8437 30.0624 35.3003 29.8083 34.8598Z" transform="translate(55 3)" fill="#905DEE"/> +</g> +</g> +</g> +</svg> + diff --git a/ci_ops/Rakefile b/ci_ops/Rakefile deleted file mode 100644 index f9338d6..0000000 --- a/ci_ops/Rakefile +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'html-proofer' - -$sourceDir = './source' -$outputDir = './output' -$testOpts = { - # Ignore errors "linking to internal hash # that does not exist" - url_ignore: ['#'], - # Allow empty alt tags (e.g. alt="") as these represent presentational images - empty_alt_ignore: true -} - -task default: ['serve:development'] - -desc 'cleans the output directory' -task :clean do - sh 'jekyll clean' -end - -namespace :build do - desc 'build development site' - task development: [:clean] do - sh 'jekyll build --drafts' - end - - desc 'build production site' - task production: [:clean] do - sh 'JEKYLL_ENV=production jekyll build --config=_config.yml' - end -end - -namespace :serve do - desc 'serve development site' - task development: [:clean] do - sh 'jekyll serve --drafts' - end - - desc 'serve production site' - task production: [:clean] do - sh 'JEKYLL_ENV=production jekyll serve --config=_config.yml' - end -end - -namespace :test do - desc 'test production build' - task production: ['build:production'] do - HTMLProofer.check_directory($outputDir, $testOpts).run - end -end diff --git a/develop/build b/develop/build index 116d2c9..31655cd 100755 --- a/develop/build +++ b/develop/build @@ -15,4 +15,4 @@ bundle exec jekyll build bundle exec htmlproofer ./_site --check-html --check-sri --disable-external bundle exec rubocop -D bundle exec develop/check-html -gem build jekyll-theme-rop.gemspec +gem build prexian.gemspec diff --git a/exe/prexian b/exe/prexian new file mode 100755 index 0000000..16f167a --- /dev/null +++ b/exe/prexian @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/prexian/cli' + +Prexian::CLI.start(ARGV) diff --git a/jekyll-theme-rop.gemspec b/jekyll-theme-rop.gemspec deleted file mode 100644 index 1f7e48f..0000000 --- a/jekyll-theme-rop.gemspec +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require_relative 'lib/rop/version' - -Gem::Specification.new do |spec| - spec.name = 'jekyll-theme-rop' - spec.version = Rop::VERSION - spec.authors = ['Ribose Inc.'] - spec.email = ['open.source@ribose.com'] - - spec.summary = 'Open Project Jekyll theme' - spec.homepage = 'https://github.com/riboseinc/jekyll-theme-rop/' - spec.license = 'MIT' - - gemspec = File.basename(__FILE__) - spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, - err: IO::NULL) do |ls| - ls.readlines("\x0", chomp: true).reject do |f| - (f == gemspec) || - !f.match(%r{^((lib|_data|_includes|_layouts|_sass|assets|_pages|_plugins)/|(_config.yml|LICENSE|README|Rakefile)((\.(txt|md|markdown)|$)))}i) - end - end - - # spec.bindir = "exe" - # spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ['lib'] - - spec.add_dependency 'fastimage' - spec.add_dependency 'git' - spec.add_dependency 'html-proofer' - spec.add_dependency 'jekyll', '~> 4.3' - spec.add_dependency 'jekyll-asciidoc' - spec.add_dependency 'jekyll-data' - spec.add_dependency 'jekyll-redirect-from' - spec.add_dependency 'jekyll-seo-tag' - spec.add_dependency 'jekyll-sitemap' - spec.add_dependency 'kramdown-parser-gfm' - spec.add_dependency 'kramdown-syntax-coderay' - - spec.add_dependency 'w3c_validators' -end diff --git a/lib/jekyll-theme-open-project.rb b/lib/jekyll-theme-open-project.rb index 541913c..acc50dd 100644 --- a/lib/jekyll-theme-open-project.rb +++ b/lib/jekyll-theme-open-project.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true # Compatibility file for old name -require_relative 'jekyll-theme-rop' +require_relative 'prexian' diff --git a/lib/jekyll-theme-rop.rb b/lib/jekyll-theme-rop.rb deleted file mode 100644 index 767d268..0000000 --- a/lib/jekyll-theme-rop.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -puts '[jekyll-theme-rop] Loaded.' - -require 'jekyll' -require_relative 'rop' - -Jekyll::Hooks.register :site, :after_init do |site| - site.reader = Rop::ProjectReader.new(site) if site.theme # TODO: Check theme name -end - -# This is a fix for static files -require 'fileutils' -Jekyll::Hooks.register :pages, :post_write do |page| - if (page.path == 'robots.txt') || (page.path == 'sitemap.xml') - File.write(page.site.in_dest_dir(page.path), page.content, - mode: 'wb') - end -end diff --git a/lib/prexian.rb b/lib/prexian.rb new file mode 100644 index 0000000..2dd7515 --- /dev/null +++ b/lib/prexian.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +puts '[prexian] Loading.' + +require 'jekyll' + +require_relative 'prexian/version' +require_relative 'prexian/git_service' +require_relative 'prexian/cli' + +require_relative 'prexian/configuration_helper' +require_relative 'prexian/collection_doc_reader' +require_relative 'prexian/site_loader' +require_relative 'prexian/project_reader' +require_relative 'prexian/hub_site_loader' +require_relative 'prexian/project_site_loader' +require_relative 'prexian/filterable_index' +require_relative 'prexian/blog_index' +require_relative 'prexian/spec_builder' +require_relative 'prexian/data_generator' + +Jekyll::Hooks.register :site, :after_init do |site| + puts "[prexian] Registering ProjectReader, theme: #{site.theme&.name || 'none'}" + site.reader = Prexian::ProjectReader.new(site) +end + +# This is a fix for static files +require 'fileutils' +Jekyll::Hooks.register :pages, :post_write do |page| + if (page.path == 'robots.txt') || (page.path == 'sitemap.xml') + File.write(page.site.in_dest_dir(page.path), page.content, mode: 'wb') + end +end + +puts '[prexian] Loaded.' diff --git a/lib/prexian/blog_index.rb b/lib/prexian/blog_index.rb new file mode 100644 index 0000000..d80af4f --- /dev/null +++ b/lib/prexian/blog_index.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'digest/md5' +require_relative 'index_generator' + +module Prexian + # + # Adds a variable holding the array of posts of open hub blog + # and from each individual project blog, combined and sorted by date. + # + # It also does some processing on the posts + # as required by the Open Project theme. + # + class CombinedPostArrayGenerator < Jekyll::Generator + include ConfigurationHelper + + safe true + + def generate(site) + @site = site + site_posts = site.posts.docs + + posts_combined = if is_hub? + project_posts = get_project_posts(site) + (project_posts + site_posts) + else + site_posts + end + + # On each post, replace authors' emails with corresponding md5 hashes + # suitable for hotlinking authors' Gravatar profile pictures. + posts_combined = posts_combined.sort_by(&:date).reverse.map do |post| + process_author(post.data['author']) if post.data.key? 'author' + + if post.data.key? 'authors' + post.data['authors'].map do |author| + process_author(author) + end + end + + post + end + + # Make combined blog post array available site-wide + prexian_config['posts_combined'] = posts_combined + prexian_config['num_posts_combined'] = posts_combined.size + end + + private + + def get_project_posts(site) + projects = get_projects(site) + + # Get documents representing posts from each project's blog + project_posts = site.collections['projects'].docs.select { |item| item.url.include? '_posts' } + + # Add parent project's data hash onto each + project_posts.map do |post| + project_name = post.url.split('/')[2] + post.data['parent_project'] = projects.detect { |p| p.data['name'] == project_name } + post.content = '' + post + end + end + + def process_author(author) + # Handle string authors (just return as-is) + return author unless author.is_a?(Hash) + + # Handle hash authors with email processing + email = author['email'] + return author if email.nil? || email.empty? + + hash = Digest::MD5.hexdigest(email) + author['email'] = hash + author['plaintext_email'] = email + author + end + + def get_projects(site) + projects = site.collections['projects'].docs.select do |item| + pieces = item.url.split('/') + pieces.length == 4 && pieces[-1] == 'index' && pieces[1] == 'projects' + end + + # Add project name (matches directory name, may differ from title) + projects.map do |project| + project.data['name'] = project.url.split('/')[2] + project + end + end + end +end diff --git a/lib/prexian/cli.rb b/lib/prexian/cli.rb new file mode 100644 index 0000000..8a87148 --- /dev/null +++ b/lib/prexian/cli.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'thor' +require_relative 'git_service' + +module Prexian + # Command-line interface for Prexian theme management + class CLI < Thor + desc 'clean', 'Clean cached Git repositories and test fixtures' + method_option :project, aliases: '-p', type: :string, desc: 'Clean cache for specific project repository URL' + method_option :all, aliases: '-a', type: :boolean, desc: 'Clean entire cache directory' + def clean + git_service = GitService.new + + if options[:project] + git_service.cleanup_cache(repo_url: options[:project]) + say "Cleaned cache for project: #{options[:project]}", :green + elsif options[:all] + git_service.cleanup_cache + say 'Cleaned entire cache directory', :green + else + git_service.cleanup_cache + say 'Cleaned entire cache directory', :green + end + + # Clean test fixture directories + cleanup_test_fixtures + rescue StandardError => e + say "Error cleaning cache: #{e.message}", :red + exit 1 + end + + desc 'status', 'Show cache status and disk usage' + def status + git_service = GitService.new + stats = git_service.cache_stats + + say "Cache Directory: #{stats[:cache_dir]}", :blue + say "Total Repositories: #{stats[:total_repos]}", :blue + say "Total Size: #{format_bytes(stats[:total_size])}", :blue + + if stats[:total_repos] > 0 + say "\nTo clean cache, run: prexian clean", :yellow + else + say "\nCache is empty", :green + end + rescue StandardError => e + say "Error getting cache status: #{e.message}", :red + exit 1 + end + + desc 'version', 'Show Prexian theme version' + def version + require_relative 'version' + say "prexian version #{Prexian::VERSION}", :blue + end + + private + + def cleanup_test_fixtures + require 'fileutils' + + directories_to_clean = [ + 'spec/fixtures/hub/_project-sites', + 'spec/fixtures/project/_hub-site', + Dir.glob('spec/fixtures/*/_site') + ].flatten + + directories_to_clean.each do |dir| + next unless Dir.exist?(dir) + + FileUtils.rm_rf(dir) + say "Cleaned directory: #{dir}", :green + end + + say 'Cleaned test fixture directories', :green if directories_to_clean.any? { |dir| Dir.exist?(dir) } + end + + def format_bytes(bytes) + units = %w[B KB MB GB TB] + size = bytes.to_f + unit_index = 0 + + while size >= 1024 && unit_index < units.length - 1 + size /= 1024 + unit_index += 1 + end + + format('%.2f %s', size, units[unit_index]) + end + end +end diff --git a/lib/prexian/collection_doc_reader.rb b/lib/prexian/collection_doc_reader.rb new file mode 100644 index 0000000..7fe670f --- /dev/null +++ b/lib/prexian/collection_doc_reader.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'jekyll' + +module Prexian + # Non-liquid document class for performance + class NonLiquidDocument < ::Jekyll::Document + def render_with_liquid? + false + end + end + + # Reads collection documents from a directory + class CollectionDocReader < ::Jekyll::DataReader + def read(dir, collection) + read_project_subdir(dir, collection) + end + + def read_project_subdir(dir, collection, nested: false) + return unless File.directory?(dir) && !@entry_filter.symlink?(dir) + + entries = Dir.chdir(dir) do + Dir['*.{adoc,md,markdown,html,svg,png}'] + Dir['*'].select { |fn| File.directory?(fn) } + end + + entries.each do |entry| + path = File.join(dir, entry) + + Jekyll.logger.debug('Prexian CollectionDocReader:', "Reading entry #{path}") + + if File.directory?(path) + read_project_subdir(path, collection, nested: true) + elsif nested || (File.basename(entry, '.*') != 'index') + process_file_entry(path, collection) + end + end + end + + private + + def process_file_entry(path, collection) + ext = File.extname(path) + + if ['.adoc', '.md', '.markdown'].include?(ext) + process_document(path, collection) + else + process_static_file(path, collection) + end + end + + def process_document(path, collection) + doc = NonLiquidDocument.new(path, site: @site, collection: collection) + doc.read + + # Add document to Jekyll document database if it refers to software or spec + doc_url_parts = doc.url.split('/') + Jekyll.logger.debug('Prexian CollectionDocReader:', + "Reading document in collection #{collection.label} with URL #{doc.url} (#{doc_url_parts.size} parts)") + + if should_add_document?(collection, doc_url_parts) + Jekyll.logger.debug('Prexian CollectionDocReader:', "Adding document with URL: #{doc.url}") + collection.docs << doc + else + Jekyll.logger.debug('Prexian CollectionDocReader:', + "Did NOT add document with URL (possibly nesting level doesn't match): #{doc.url}") + end + end + + def process_static_file(path, collection) + Jekyll.logger.debug('Prexian CollectionDocReader:', "Adding static file: #{path}") + collection.files << ::Jekyll::StaticFile.new( + @site, + @site.source, + Pathname.new(File.dirname(path)).relative_path_from(Pathname.new(@site.source)).to_s, + File.basename(path), + collection + ) + end + + def should_add_document?(collection, doc_url_parts) + (collection.label != 'projects') || (doc_url_parts.size == 5) + end + end +end diff --git a/lib/prexian/configuration_helper.rb b/lib/prexian/configuration_helper.rb new file mode 100644 index 0000000..a8c2e38 --- /dev/null +++ b/lib/prexian/configuration_helper.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Prexian + # Provides centralized access to Prexian configuration patterns + # that are repeated across multiple files + module ConfigurationHelper + def prexian_config + @prexian_config ||= (@config || @site.config)['prexian'] || {} + end + + def default_repo_branch + prexian_config['default_repo_branch'] || 'main' + end + + def refresh_condition + prexian_config['refresh_remote_data'] || 'last-resort' + end + + def site_type + prexian_config['site_type'] || 'project' + end + + def is_hub? + site_type == 'hub' + end + + def is_project? + site_type == 'project' + end + + def collection_name_for(index_name) + is_hub? ? 'projects' : index_name + end + + # Common configuration values used across generators + def max_featured_items + 3 + end + + def default_time + @default_time ||= Time.new(1989, 12, 31, 0, 0, 0, '+00:00') + end + end +end diff --git a/lib/prexian/content_entry.rb b/lib/prexian/content_entry.rb new file mode 100644 index 0000000..6a11f9d --- /dev/null +++ b/lib/prexian/content_entry.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +require_relative 'spec_builder' + +module Prexian + # Abstract base class for different types of content entries + class ContentEntry + attr_reader :index_doc, :collection_name, :site + + def initialize(index_doc, collection_name, site) + @index_doc = index_doc + @collection_name = collection_name + @site = site + end + + # Abstract methods that subclasses must implement + def filter_field + raise NotImplementedError, "Subclasses must implement filter_field" + end + + def extract_config + raise NotImplementedError, "Subclasses must implement extract_config" + end + + def checkout_destination + raise NotImplementedError, "Subclasses must implement checkout_destination" + end + + # Default implementations that can be overridden + def needs_build? + false + end + + def perform_build(config) + # Default: no build step + end + + def post_process(checkout_result) + # Default: no post-processing + end + + protected + + def item_name + @item_name ||= @index_doc.id.split('/')[-1] + end + + def default_repo_branch + 'main' + end + end + + # Software entry processor + class SoftwareEntry < ContentEntry + def filter_field + 'repo_url' + end + + def extract_config + docs = @index_doc.data['docs'] + main_repo = @index_doc.data['repo_url'] + main_repo_branch = @index_doc.data['repo_branch'] || default_repo_branch + + { + repo_url: (docs && docs['git_repo_url']) || main_repo, + branch: (docs && docs['git_repo_branch']) || main_repo_branch, + subtree: (docs && docs['git_repo_subtree']) || 'docs' + } + end + + def checkout_destination + "#{@index_doc.path.split('/')[0..-2].join('/')}/#{item_name}" + end + end + + # Specification entry processor + class SpecificationEntry < ContentEntry + def filter_field + 'spec_source' + end + + def extract_config + src = @index_doc.data['spec_source'] + build = src['build'] || {} + + spec_checkout_path = checkout_destination + spec_root = src['git_repo_subtree'] ? "#{spec_checkout_path}/#{src['git_repo_subtree']}" : spec_checkout_path + + config = { + repo_url: src['git_repo_url'], + branch: src['git_repo_branch'] || default_repo_branch, + subtree: src['git_repo_subtree'], + spec_root: spec_root + } + + if !build['engine'].nil? && !build['engine'].empty? + unless SpecBuilder.valid_engine?(build['engine']) + raise ArgumentError, "Invalid engine specified: #{build['engine']}" + end + + config.merge!( + engine: build['engine'], + engine_opts: build['engine_opts'] || {} + ) + end + + config + end + + def checkout_destination + "#{@index_doc.path.split('/')[0..-2].join('/')}/#{item_name}" + end + + def needs_build? + config = extract_config + config[:engine] && config[:spec_root] + end + + def perform_build(config) + return unless needs_build? + + builder = SpecBuilder.new( + @site, + @index_doc, + config[:spec_root], + "specs/#{item_name}", + config[:engine], + config[:engine_opts] + ) + + builder.build + builder.built_pages.each do |page| + @site.pages << page + end + rescue StandardError => e + Jekyll.logger.error("Prexian ContentEntry: Failed to build spec pages for #{@index_doc.id}: #{e.message}") + end + end + + # Project entry processor (for hub sites) + class ProjectEntry < ContentEntry + def filter_field + 'site' + end + + def extract_config + site_config = @index_doc.data['site'] + + # Support both git_repo_url (for Git repositories) and local_path (for local directories) + if site_config['local_path'] + { + repo_url: site_config['local_path'], + branch: nil # Not applicable for local paths + } + else + { + repo_url: site_config['git_repo_url'], + branch: site_config['git_repo_branch'] || default_repo_branch + } + end + end + + def checkout_destination + File.join(@site.source, '_project-sites', item_name) + end + + def post_process(checkout_result) + add_to_includes_load_paths(checkout_result[:local_path]) + end + + private + + def add_to_includes_load_paths(project_path) + return unless File.directory?(project_path) + + project_sites_dir = File.join(@site.source, '_project-sites') + + # Add _project-sites directory to Jekyll's includes_load_paths + Jekyll.logger.debug("Prexian ProjectEntry: Adding #{project_sites_dir} to includes_load_paths") + @site.config['includes_load_paths'] ||= [] + unless @site.config['includes_load_paths'].include?(project_sites_dir) + @site.config['includes_load_paths'] << project_sites_dir + end + end + end +end diff --git a/lib/prexian/data_generator.rb b/lib/prexian/data_generator.rb new file mode 100644 index 0000000..d42666d --- /dev/null +++ b/lib/prexian/data_generator.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +require_relative 'index_generator' + +module Prexian + # Generator that populates site.prexian.* data for use in templates + class DataGenerator < Jekyll::Generator + include ConfigurationHelper + + safe true + priority :low + + def generate(site) + @site = site + + # Initialize prexian data namespace + site.config['prexian'] ||= {} + prexian_data = site.config['prexian'] + + # Process projects for hub sites + if is_hub? + process_projects(site, prexian_data) + end + + # Process software and specs for all sites + process_software(site, prexian_data) + process_specs(site, prexian_data) + process_posts(site, prexian_data) + end + + private + + def process_projects(site, prexian_data) + return unless site.collections['projects'] + + projects = get_projects(site) + + # Set basic project data + prexian_data['projects'] = projects + prexian_data['num_projects'] = projects.size + + # Categorize featured vs non-featured projects + featured_projects = projects.select { |p| p.data['featured'] } + prexian_data['featured_projects'] = featured_projects + prexian_data['num_featured_projects'] = featured_projects.size + + non_featured_projects = projects.reject { |p| p.data['featured'] } + prexian_data['non_featured_projects'] = non_featured_projects + prexian_data['num_non_featured_projects'] = non_featured_projects.size + end + + def process_software(site, prexian_data) + return unless site.collections['software'] + + software_items = site.collections['software'].docs.select do |item| + item.path.include?('/_software') && !item.path.include?('/docs') + end + + sort_items_by_date(software_items) + add_project_data_to_items(site, software_items) if is_hub? + + set_index_config(software_items, 'software') + categorize_items(software_items, 'software') + + # Copy to prexian namespace + prexian_data['all_software'] = software_items + prexian_data['num_all_software'] = software_items.size + prexian_data['featured_software'] = prexian_data['featured_software'] || [] + prexian_data['num_featured_software'] = prexian_data['featured_software'].size + prexian_data['non_featured_software'] = prexian_data['non_featured_software'] || [] + prexian_data['num_non_featured_software'] = prexian_data['non_featured_software'].size + prexian_data['one_software'] = software_items.size == 1 + end + + def process_specs(site, prexian_data) + return unless site.collections['specs'] + + spec_items = site.collections['specs'].docs.select do |item| + item.path.include?('/_specs') && !item.path.include?('/docs') + end + + sort_items_by_date(spec_items) + add_project_data_to_items(site, spec_items) if is_hub? + + set_index_config(spec_items, 'specs') + categorize_items(spec_items, 'specs') + + # Copy to prexian namespace + prexian_data['all_specs'] = spec_items + prexian_data['num_all_specs'] = spec_items.size + prexian_data['featured_specs'] = prexian_data['featured_specs'] || [] + prexian_data['num_featured_specs'] = prexian_data['featured_specs'].size + prexian_data['non_featured_specs'] = prexian_data['non_featured_specs'] || [] + prexian_data['num_non_featured_specs'] = prexian_data['non_featured_specs'].size + end + + def process_posts(site, prexian_data) + return unless site.collections['posts'] + + posts = site.collections['posts'].docs + sort_items_by_date(posts, 'date') + + # For hub sites, combine posts from all projects + if is_hub? + combined_posts = posts.dup + + # Add posts from project sites if they exist + site.collections['projects']&.docs&.each do |project| + project_name = project.data['name'] || project.url.split('/')[2] + project_posts_path = File.join(site.source, '_project-sites', project_name, '_posts') + + if Dir.exist?(project_posts_path) + Dir.glob(File.join(project_posts_path, '*.md')).each do |post_file| + # Create document for project post + project_post = Jekyll::Document.new( + post_file, + site: site, + collection: site.collections['posts'] + ) + project_post.read + project_post.data['project_name'] = project_name + combined_posts << project_post + end + end + end + + sort_items_by_date(combined_posts, 'date') + prexian_data['posts_combined'] = combined_posts + prexian_data['num_posts_combined'] = combined_posts.size + end + + prexian_data['posts'] = posts + prexian_data['num_posts'] = posts.size + end + + def set_index_config(items, index_name) + prexian_data = @site.config['prexian'] + prexian_data["one_#{index_name}"] = items.size == 1 ? items[0] : nil + prexian_data["all_#{index_name}"] = items + prexian_data["num_all_#{index_name}"] = items.size + end + + def categorize_items(items, index_name) + prexian_data = @site.config['prexian'] + + featured_items = items.select { |item| item.data['featured'] } + prexian_data["featured_#{index_name}"] = featured_items + prexian_data["num_featured_#{index_name}"] = featured_items.size + + non_featured_items = items.reject { |item| item.data['featured'] } + prexian_data["non_featured_#{index_name}"] = non_featured_items + prexian_data["num_non_featured_#{index_name}"] = non_featured_items.size + end + + def sort_items_by_date(items, date_field = 'last_update') + items.sort! do |i1, i2| + val1 = i1.data.fetch(date_field, default_time) || default_time + val2 = i2.data.fetch(date_field, default_time) || default_time + (val2 <=> val1) || 0 + end + end + + def add_project_data_to_items(site, items) + return items unless is_hub? + + items.map! do |item| + project_name = item.url.split('/')[2] + project_path = "_projects/#{project_name}/index.md" + + item.data['project_name'] = project_name + item.data['project_data'] = site.collections['projects'].docs.select do |proj| + proj.path.end_with? project_path + end [0] + + item + end + end + + def get_projects(site) + projects = site.collections['projects'].docs.select do |item| + pieces = item.url.split('/') + pieces.length == 4 && pieces[-1] == 'index' && pieces[1] == 'projects' + end + + # Add project name (matches directory name, may differ from title) + projects.map do |project| + project.data['name'] = project.url.split('/')[2] + project + end + end + + def default_time + Time.new(1970, 1, 1) + end + end +end diff --git a/lib/prexian/filterable_index.rb b/lib/prexian/filterable_index.rb new file mode 100644 index 0000000..5ccd042 --- /dev/null +++ b/lib/prexian/filterable_index.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require_relative 'index_generator' + +module Prexian + # On an open hub site, Jekyll Open Project theme assumes the existence of two types + # of item indexes: software and specs, where items are gathered + # from across open projects in the hub. + # + # The need for :item_test arises from our data structure (see Jekyll Open Project theme docs) + # and the fact that Jekyll doesn't intuitively handle nested collections. + + # Below passes the `items` variable to normal (unfiltered) + # index page layout. + + class IndexPageGenerator < Jekyll::Generator + include ConfigurationHelper + + safe true + + def generate(site) + @site = site + prexian_config['max_featured_software'] = max_featured_items + prexian_config['max_featured_specs'] = max_featured_items + prexian_config['max_featured_posts'] = max_featured_items + + IndexConfig.all_indexes.each do |index_name, params| + collection_name = collection_name_for(index_name) + + next unless site.collections.key? collection_name + + # Filters items from given collection_name through item_test function + # and makes items available in templates via e.g. site.all_specs, site.all_software + + items = get_all_items(site, collection_name, params[:item_test]) + + set_index_config(items, index_name) + categorize_items(items, index_name) + end + end + + def get_all_items(site, collection_name, filter_func) + # Fetches items of specified type, ordered and prepared for usage in index templates + + collection = site.collections[collection_name] + + raise "Collection does not exist: #{collection_name}" if collection.nil? + + items = collection.docs.select do |item| + filter_func.call(item) + end + + sort_items_by_date(items) + add_project_data_to_items(site, items) + end + + private + + def sort_items_by_date(items, date_field = 'last_update') + items.sort! do |i1, i2| + val1 = i1.data.fetch(date_field, default_time) || default_time + val2 = i2.data.fetch(date_field, default_time) || default_time + (val2 <=> val1) || 0 + end + end + + def add_project_data_to_items(site, items) + return items unless is_hub? + + items.map! do |item| + project_name = item.url.split('/')[2] + project_path = "_projects/#{project_name}/index.md" + + item.data['project_name'] = project_name + item.data['project_data'] = site.collections['projects'].docs.select do |proj| + proj.path.end_with? project_path + end [0] + + item + end + end + + def categorize_items(items, index_name) + featured_items = items.reject { |item| item.data['feature_with_priority'].nil? } + prexian_config["featured_#{index_name}"] = featured_items.sort_by { |item| item.data['feature_with_priority'] } + prexian_config["num_featured_#{index_name}"] = featured_items.size + + non_featured_items = items.select { |item| item.data['feature_with_priority'].nil? } + prexian_config["non_featured_#{index_name}"] = non_featured_items + prexian_config["num_non_featured_#{index_name}"] = non_featured_items.size + end + + def set_index_config(items, index_name) + prexian_config["one_#{index_name}"] = items[0] if items.length == 1 + prexian_config["all_#{index_name}"] = items + prexian_config["num_all_#{index_name}"] = items.size + end + + def collection_name_for(index_name) + index_name + end + + def max_featured_items + 3 + end + + def default_time + Time.new(1970, 1, 1) + end + end + + # Each software or spec item can have its tags, + # and the theme allows to filter each index by a tag. + # The below generates an additional index page + # for each tag in an index, like software/Ruby. + # + # Note: this expects "_pages/<index page>.html" to be present in site source, + # so it would fail if theme setup instructions were not followed fully. + + class FilteredIndexPageGenerator < IndexPageGenerator + def generate(site) + @site = site + IndexConfig.all_indexes.each do |index_name, params| + collection_name = collection_name_for(index_name) + + items = get_all_items(site, collection_name, params[:item_test]) + + # Creates a data structure like { tag1: [item1, item2], tag2: [item2, item3] } + tags = {} + items.each do |item| + (item.data['tags'] or []).each do |tag| + tags[tag] = [] unless tags.key? tag + tags[tag].push(item) + end + end + + # Creates a filtered index page for each tag + tags.each do |tag, tagged_items| + site.pages << FilteredIndexPage.new( + site, + site.source, + # The filtered page will be nested under /<index page>/<tag>.html + File.join(index_name, tag), + tag, + tagged_items, + index_name + ) + end + end + end + end +end diff --git a/lib/prexian/git_content_processor.rb b/lib/prexian/git_content_processor.rb new file mode 100644 index 0000000..07b8379 --- /dev/null +++ b/lib/prexian/git_content_processor.rb @@ -0,0 +1,298 @@ +# frozen_string_literal: true + +require_relative 'content_entry' +require_relative 'git_service' +require_relative 'collection_doc_reader' + +# Load entry classes for type checking +module Prexian + class ProjectEntry < ContentEntry; end + class SoftwareEntry < ContentEntry; end + class SpecificationEntry < ContentEntry; end +end + +module Prexian + # Generic processor for git-based content entries + class GitContentProcessor + def initialize(site, git_service: nil, collection_reader: nil, site_loader: nil) + @site = site + @git_service = git_service || GitService.new + @collection_reader = collection_reader || CollectionDocReader.new(@site) + @site_loader = site_loader + end + + # Process a collection using the specified entry class + def process_collection(collection_name, entry_class, refresh_condition: 'last-resort') + return unless @site.collections.key?(collection_name) + + Jekyll.logger.debug("Prexian GitContentProcessor: Processing #{collection_name} collection with #{entry_class}") + puts "Prexian GitContentProcessor: About to call find_entry_points for #{entry_class}" + + # Special handling for SoftwareEntry and SpecificationEntry when processing projects + if collection_name == 'projects' && (entry_class.name == 'Prexian::SoftwareEntry' || entry_class.name == 'Prexian::SpecificationEntry') + process_project_content(entry_class, refresh_condition: refresh_condition) + return + end + + entry_points = find_entry_points(collection_name, entry_class) + puts "Prexian GitContentProcessor: find_entry_points returned #{entry_points.length} entries" + + if entry_points.empty? + Jekyll.logger.info("Prexian GitContentProcessor: No entry points found in #{collection_name} for #{entry_class}") + return + end + + entry_points.each do |index_doc| + puts "Prexian GitContentProcessor: Creating #{entry_class} for doc #{index_doc.id}" + begin + entry = entry_class.new(index_doc, collection_name, @site) + puts "Prexian GitContentProcessor: Successfully created entry, now processing..." + process_entry(entry, refresh_condition: refresh_condition) + rescue => e + puts "Prexian GitContentProcessor: Error creating or processing entry: #{e.message}" + puts "Prexian GitContentProcessor: Backtrace: #{e.backtrace.first(5).join("\n")}" + end + end + end + + private + + def get_filter_field_for_class(entry_class) + case entry_class.name + when 'Prexian::SoftwareEntry' + 'repo_url' + when 'Prexian::SpecificationEntry' + 'spec_source' + when 'Prexian::ProjectEntry' + 'site' + else + # Fallback: create a temporary instance if needed + require 'ostruct' + temp_doc = OpenStruct.new(id: 'temp', data: {}, path: 'temp') + temp_entry = entry_class.new(temp_doc, 'temp', @site) + temp_entry.filter_field + end + end + + def find_entry_points(collection_name, entry_class) + collection = @site.collections[collection_name] + return [] unless collection && !collection.docs.empty? + + # Get the filter field from the entry class directly + filter_field = get_filter_field_for_class(entry_class) + + Jekyll.logger.debug("Prexian GitContentProcessor: Looking for #{entry_class} in #{collection_name} collection with filter_field '#{filter_field}'") + Jekyll.logger.debug("Prexian GitContentProcessor: Collection has #{@site.collections[collection_name].docs.length} documents") + + # Filter documents based on the entry type's filter field + matching_docs = @site.collections[collection_name].docs.select do |doc| + Jekyll.logger.debug("Prexian GitContentProcessor: Checking doc #{doc.id}, data keys: #{doc.data.keys}") + case filter_field + when 'site' + # Special case for ProjectEntry - check for either git_repo_url or local_path + has_site = doc.data['site'] && (doc.data['site']['git_repo_url'] || doc.data['site']['local_path']) + Jekyll.logger.debug("Prexian GitContentProcessor: Doc #{doc.id} has site config: #{has_site}") + has_site + else + has_field = doc.data[filter_field] + Jekyll.logger.debug("Prexian GitContentProcessor: Doc #{doc.id} has #{filter_field}: #{has_field}") + has_field + end + end + + Jekyll.logger.debug("Prexian GitContentProcessor: Found #{matching_docs.length} matching documents for #{entry_class}") + matching_docs + end + + def process_entry(entry, refresh_condition: 'last-resort') + Jekyll.logger.debug("Prexian GitContentProcessor: Processing entry #{entry.index_doc.id}") + puts "Prexian GitContentProcessor: Processing entry #{entry.index_doc.id}" + + config = entry.extract_config + puts "Prexian GitContentProcessor: Extracted config: #{config.inspect}" + + destination = entry.checkout_destination + puts "Prexian GitContentProcessor: Checkout destination: #{destination}" + + begin + # Check if this is a local path (indicated by branch being nil and path being local) + resolved_path = resolve_local_path(config[:repo_url]) + if config[:branch].nil? && resolved_path && File.directory?(resolved_path) + puts "Prexian GitContentProcessor: Handling local path: #{config[:repo_url]} -> #{resolved_path}" + checkout_result = handle_local_path(resolved_path, destination) + else + puts "Prexian GitContentProcessor: Handling git repository: #{config[:repo_url]}" + # Use GitService to handle checkout and copy + checkout_result = @git_service.checkout_and_copy_content( + config, + destination, + subtrees: config[:subtree] ? [config[:subtree]] : [], + refresh_condition: refresh_condition + ) + end + + puts "Prexian GitContentProcessor: Checkout result: #{checkout_result.inspect}" + + # For ProjectEntry, we don't read content into collections - that's handled later + # by SoftwareEntry and SpecificationEntry processing + if entry.is_a?(ProjectEntry) + puts "Prexian GitContentProcessor: Skipping collection read for ProjectEntry - content will be processed by subsequent SoftwareEntry/SpecificationEntry" + else + # Read the copied content into collections for other entry types + puts "Prexian GitContentProcessor: Reading content from #{destination} into collection #{entry.collection_name}" + puts "Prexian GitContentProcessor: Available collections: #{@site.collections.keys}" + @collection_reader.read(destination, @site.collections[entry.collection_name]) + end + + # Update document metadata + entry.index_doc.merge_data!({ 'last_update' => checkout_result[:modified_at] }) + + # Perform any post-processing (e.g., asset copying for projects) + entry.post_process(checkout_result) + + # Handle build step if needed + if entry.needs_build? + entry.perform_build(config) + end + + rescue Prexian::GitService::GitError => e + handle_git_error(entry, e) + rescue => e + puts "Prexian GitContentProcessor: Error processing entry: #{e.message}" + puts "Prexian GitContentProcessor: Backtrace: #{e.backtrace.first(5).join("\n")}" + end + end + + def handle_git_error(entry, error) + Jekyll.logger.warn("Prexian GitContentProcessor: Git error for #{entry.index_doc.id}: #{error.message}") + + # For software entries, try fallback to main repository for timestamp + if entry.is_a?(SoftwareEntry) + handle_software_fallback(entry) + end + end + + def resolve_local_path(path) + return nil if path.nil? || path.empty? + + # If it's already an absolute path, return it + return path if File.absolute_path?(path) + + # For relative paths, resolve them relative to the site source directory + resolved = File.expand_path(path, @site.source) + puts "Prexian GitContentProcessor: Resolved relative path '#{path}' to '#{resolved}'" + resolved + end + + def handle_local_path(source_path, destination) + require 'fileutils' + + puts "Prexian GitContentProcessor: Copying from #{source_path} to #{destination}" + + # Create destination directory if it doesn't exist + FileUtils.mkdir_p(destination) + + # Copy all content from source to destination, but skip existing collection directories + # to avoid nested _software/_software and _specs/_specs + Dir.glob("#{source_path}/*", File::FNM_DOTMATCH).each do |item| + next if File.basename(item) == '.' || File.basename(item) == '..' + + item_name = File.basename(item) + dest_item = File.join(destination, item_name) + + # Skip if destination already has this directory (avoid duplicates) + if File.directory?(item) && File.directory?(dest_item) + puts "Prexian GitContentProcessor: Skipping existing directory: #{item_name}" + next + end + + if File.directory?(item) + FileUtils.cp_r(item, dest_item) + else + FileUtils.cp(item, dest_item) + end + end + + # Get the modification time of the source directory + modified_at = File.mtime(source_path) + + { + local_path: destination, + modified_at: modified_at + } + end + + def process_project_content(entry_class, refresh_condition: 'last-resort') + puts "Prexian GitContentProcessor: Processing project content for #{entry_class}" + + # Determine target collection based on entry class + target_collection_name = case entry_class.name + when 'Prexian::SoftwareEntry' + 'software' + when 'Prexian::SpecificationEntry' + 'specs' + else + puts "Prexian GitContentProcessor: Unknown entry class #{entry_class.name}" + return + end + + target_collection = @site.collections[target_collection_name] + unless target_collection + puts "Prexian GitContentProcessor: Target collection #{target_collection_name} not found" + return + end + + # Find project sites directory + project_sites_dir = File.join(@site.source, '_project-sites') + unless File.directory?(project_sites_dir) + puts "Prexian GitContentProcessor: Project sites directory not found: #{project_sites_dir}" + return + end + + puts "Prexian GitContentProcessor: Scanning project sites in #{project_sites_dir}" + + # Scan each project directory + Dir.glob("#{project_sites_dir}/*").each do |project_dir| + next unless File.directory?(project_dir) + + project_name = File.basename(project_dir) + puts "Prexian GitContentProcessor: Processing project #{project_name}" + + # Determine subdirectory to scan based on entry class + subdir = case entry_class.name + when 'Prexian::SoftwareEntry' + File.join(project_dir, '_software') + when 'Prexian::SpecificationEntry' + File.join(project_dir, '_specs') + end + + if File.directory?(subdir) + puts "Prexian GitContentProcessor: Reading content from #{subdir} into #{target_collection_name} collection" + @collection_reader.read(subdir, target_collection) + else + puts "Prexian GitContentProcessor: Subdirectory not found: #{subdir}" + end + end + end + + def handle_software_fallback(entry) + Jekyll.logger.warn("Prexian GitContentProcessor: Trying main repo fallback for #{entry.index_doc.id}") + + main_repo_config = { + repo_url: entry.index_doc.data['repo_url'], + branch: entry.index_doc.data['repo_branch'] || 'main' + } + + begin + checkout_result = @git_service.shallow_checkout( + main_repo_config[:repo_url], + branch: main_repo_config[:branch], + refresh_condition: 'last-resort' + ) + entry.index_doc.merge_data!({ 'last_update' => checkout_result[:modified_at] }) + rescue Prexian::GitService::GitError => fallback_error + Jekyll.logger.error("Prexian GitContentProcessor: Fallback also failed for #{entry.index_doc.id}: #{fallback_error.message}") + end + end + end +end diff --git a/lib/prexian/git_service.rb b/lib/prexian/git_service.rb new file mode 100644 index 0000000..244f530 --- /dev/null +++ b/lib/prexian/git_service.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'git' +require 'digest' +require 'jekyll' + +module Prexian + # Service class for handling Git operations with external caching + class GitService + DEFAULT_REPO_REMOTE_NAME = 'origin' + DEFAULT_REPO_BRANCH = 'main' + DEFAULT_CACHE_DIR = File.expand_path('~/.prexian/cache/repos') + + class GitError < StandardError; end + class SparseCheckoutError < GitError; end + + def initialize(cache_dir: nil, logger: nil) + @cache_dir = cache_dir || ENV['ROP_CACHE_DIR'] || DEFAULT_CACHE_DIR + @logger = logger || default_logger + ensure_cache_dir_exists + end + + # Shallow clone or update a repository with optional sparse checkout + # Returns hash with success status, timestamp, and local path + def shallow_checkout(repo_url, sparse_subtrees: [], branch: nil, refresh_condition: 'last-resort') + # Check if repo_url is a file path + return handle_file_path_checkout(repo_url) if File.exist?(repo_url) && File.directory?(repo_url) + + repo_hash = generate_repo_hash(repo_url) + branch_name = branch || DEFAULT_REPO_BRANCH + repo_path = File.join(@cache_dir, repo_hash, branch_name) + + @logger.debug("Prexian GitService: Checking out #{repo_url} (#{branch_name}) to #{repo_path}") + + result = perform_checkout(repo_path, repo_url, sparse_subtrees, branch_name, refresh_condition) + result[:local_path] = repo_path + result + rescue StandardError => e + @logger.error("Prexian GitService: Failed to checkout #{repo_url}: #{e.message}") + raise GitError, "Git checkout failed for #{repo_url}: #{e.message}" + end + + # Copy files from cached repository to destination + def copy_cached_content(cached_repo_path, destination_path, subtrees: []) + return false unless File.exist?(cached_repo_path) + + FileUtils.mkdir_p(destination_path) + + if subtrees.empty? + # Copy entire repository content (excluding .git) + Dir.glob(File.join(cached_repo_path, '*')).each do |item| + next if File.basename(item) == '.git' + + FileUtils.cp_r(item, destination_path) + end + else + # Copy only specified subtrees + subtrees.each do |subtree| + source_path = File.join(cached_repo_path, subtree) + next unless File.exist?(source_path) + + dest_subtree_path = File.join(destination_path, subtree) + FileUtils.mkdir_p(File.dirname(dest_subtree_path)) + FileUtils.cp_r(source_path, dest_subtree_path) + end + end + + true + rescue StandardError => e + @logger.error("Prexian GitService: Failed to copy content: #{e.message}") + false + end + + # Clean up cached repositories + def cleanup_cache(repo_url: nil) + if repo_url + repo_hash = generate_repo_hash(repo_url) + repo_cache_path = File.join(@cache_dir, repo_hash) + FileUtils.rm_rf(repo_cache_path) if File.exist?(repo_cache_path) + @logger.info("Prexian GitService: Cleaned cache for #{repo_url}") + else + FileUtils.rm_rf(@cache_dir) if File.exist?(@cache_dir) + @logger.info('Prexian GitService: Cleaned entire cache directory') + end + end + + # High-level method that combines checkout and copy operations + # This consolidates the repetitive pattern used across site readers + def checkout_and_copy_content(repo_config, destination_path, options = {}) + subtrees = options[:subtrees] || [] + refresh_condition = options[:refresh_condition] || 'last-resort' + + # Perform git checkout + checkout_result = shallow_checkout( + repo_config[:repo_url], + branch: repo_config[:branch], + refresh_condition: refresh_condition + ) + + # Copy content from cache to destination + copy_success = copy_cached_content( + checkout_result[:local_path], + destination_path, + subtrees: subtrees + ) + + unless copy_success + raise GitError, "Failed to copy content from #{checkout_result[:local_path]} to #{destination_path}" + end + + checkout_result + rescue GitError => e + @logger.warn("GitService: checkout_and_copy_content failed: #{e.message}") + raise e + end + + # Get cache statistics + def cache_stats + return { total_repos: 0, total_size: 0 } unless File.exist?(@cache_dir) + + total_size = 0 + repo_count = 0 + + Dir.glob(File.join(@cache_dir, '*')).each do |repo_dir| + next unless File.directory?(repo_dir) + + repo_count += 1 + total_size += directory_size(repo_dir) + end + + { + total_repos: repo_count, + total_size: total_size, + cache_dir: @cache_dir + } + end + + private + + def default_logger + # Try to use Jekyll logger if available, otherwise create a simple logger + if defined?(Jekyll) + Jekyll.logger + else + require 'logger' + Logger.new($stdout).tap do |logger| + logger.level = Logger::INFO + end + end + end + + def handle_file_path_checkout(file_path) + # For file paths, we pretend it's a successful checkout + # and return the path as-is with current timestamp + { + success: true, + newly_initialized: false, + modified_at: File.mtime(file_path), + local_path: file_path + } + end + + def ensure_cache_dir_exists + FileUtils.mkdir_p(@cache_dir) unless File.exist?(@cache_dir) + end + + def generate_repo_hash(repo_url) + # Create a hash from the repository URL for consistent directory naming + Digest::SHA256.hexdigest(repo_url)[0..15] + end + + def perform_checkout(repo_path, remote_url, sparse_subtrees, branch_name, refresh_condition) + newly_initialized = false + repo = nil + + git_dir = File.join(repo_path, '.git') + + if File.exist?(git_dir) + repo = Git.open(repo_path) + else + newly_initialized = true + repo = initialize_new_repo(repo_path, remote_url, sparse_subtrees) + end + + handle_refresh_condition(repo, branch_name, refresh_condition, sparse_subtrees) + + latest_commit = repo.gcommit('HEAD') + + { + success: true, + newly_initialized: newly_initialized, + modified_at: latest_commit.date + } + end + + def initialize_new_repo(repo_path, remote_url, sparse_subtrees) + FileUtils.mkdir_p(repo_path) + repo = Git.init(repo_path) + + repo.config('core.sshCommand', 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no') + repo.add_remote(DEFAULT_REPO_REMOTE_NAME, remote_url) + + setup_sparse_checkout(repo, repo_path, sparse_subtrees) if sparse_subtrees.any? + + repo + end + + def setup_sparse_checkout(repo, repo_path, sparse_subtrees) + repo.config('core.sparseCheckout', true) + + git_info_dir = File.join(repo_path, '.git', 'info') + FileUtils.mkdir_p(git_info_dir) + + sparse_checkout_file = File.join(git_info_dir, 'sparse-checkout') + File.open(sparse_checkout_file, 'w') do |f| + sparse_subtrees.each { |path| f.puts(path) } + end + end + + def handle_refresh_condition(repo, branch_name, refresh_condition, sparse_subtrees) + case refresh_condition + when 'always' + fetch_and_checkout(repo, branch_name) + when 'last-resort' + handle_last_resort_refresh(repo, branch_name, sparse_subtrees) + when 'skip' + # Do nothing - use existing checkout + else + raise GitError, "Invalid refresh_remote_data value: #{refresh_condition}" + end + end + + def fetch_and_checkout(repo, branch_name) + repo.fetch(DEFAULT_REPO_REMOTE_NAME, { depth: 1 }) + repo.reset_hard + repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{branch_name}", { f: true }) + end + + def handle_last_resort_refresh(repo, branch_name, sparse_subtrees) + repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{branch_name}", { f: true }) + rescue StandardError => e + if sparse_checkout_error?(e, sparse_subtrees) + raise SparseCheckoutError, "Sparse checkout failed for subtrees: #{sparse_subtrees.join(', ')}" + end + + @logger.debug("Prexian GitService: Checkout failed, fetching: #{e.message}") + fetch_and_checkout(repo, branch_name) + end + + def sparse_checkout_error?(error, subtrees) + error.message.include?('Sparse checkout leaves no entry on working directory') && subtrees.any? + end + + def directory_size(path) + total_size = 0 + Dir.glob(File.join(path, '**', '*'), File::FNM_DOTMATCH).each do |file| + total_size += File.size(file) if File.file?(file) + end + total_size + end + end +end diff --git a/lib/prexian/hub_site_loader.rb b/lib/prexian/hub_site_loader.rb new file mode 100644 index 0000000..78a83c2 --- /dev/null +++ b/lib/prexian/hub_site_loader.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative 'site_loader' +require_relative 'content_entry' + +module Prexian + # Handles reading and aggregating content for hub sites + # Inherits all base site functionality from SiteLoader + class HubSiteLoader < SiteLoader + def read_content + # Hub sites get all project site functionality + super + + # Plus hub-specific project aggregation + read_projects + end + + def read_projects + return unless is_hub? + + puts 'Prexian HubSiteLoader: Starting to read projects for hub site' + puts "Prexian HubSiteLoader: Projects collection exists: #{@site.collections.key?('projects')}" + if @site.collections.key?('projects') + puts "Prexian HubSiteLoader: Projects collection has #{@site.collections['projects'].docs.length} documents" + @site.collections['projects'].docs.each do |doc| + puts "Prexian HubSiteLoader: Project doc: #{doc.id}, data keys: #{doc.data.keys}" + end + end + + puts 'Prexian HubSiteLoader: Processing ProjectEntry...' + @content_processor.process_collection('projects', ProjectEntry, refresh_condition: refresh_condition) + + # Process software and specs for projects using inherited methods + puts 'Prexian HubSiteLoader: Processing SoftwareEntry for projects...' + @content_processor.process_collection('projects', SoftwareEntry, refresh_condition: refresh_condition) + puts 'Prexian HubSiteLoader: Processing SpecificationEntry for projects...' + @content_processor.process_collection('projects', SpecificationEntry, refresh_condition: refresh_condition) + end + end +end diff --git a/lib/prexian/index_generator.rb b/lib/prexian/index_generator.rb new file mode 100644 index 0000000..10dfeb6 --- /dev/null +++ b/lib/prexian/index_generator.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require_relative 'configuration_helper' + +module Prexian + # Base class for all index generators that provides common functionality + # for processing collections and generating index pages + class BaseIndexGenerator + include ConfigurationHelper + + protected + + # Common method to sort items by date (last_update or date field) + def sort_items_by_date(items, date_field = 'last_update') + items.sort! do |i1, i2| + val1 = i1.data.fetch(date_field, default_time) || default_time + val2 = i2.data.fetch(date_field, default_time) || default_time + (val2 <=> val1) || 0 + end + end + + # Common method to add project data to items in hub sites + def add_project_data_to_items(site, items) + return items unless is_hub? + + items.map! do |item| + project_name = item.url.split('/')[2] + project_path = "_projects/#{project_name}/index.md" + + item.data['project_name'] = project_name + item.data['project_data'] = site.collections['projects'].docs.select do |proj| + proj.path.end_with? project_path + end [0] + + item + end + end + + # Common method to categorize items into featured and non-featured + def categorize_items(items, index_name) + featured_items = items.reject { |item| item.data['feature_with_priority'].nil? } + prexian_config["featured_#{index_name}"] = featured_items.sort_by { |item| item.data['feature_with_priority'] } + prexian_config["num_featured_#{index_name}"] = featured_items.size + + non_featured_items = items.select { |item| item.data['feature_with_priority'].nil? } + prexian_config["non_featured_#{index_name}"] = non_featured_items + prexian_config["num_non_featured_#{index_name}"] = non_featured_items.size + end + + # Common method to set configuration values for an index + def set_index_config(items, index_name) + prexian_config["one_#{index_name}"] = items[0] if items.length == 1 + prexian_config["all_#{index_name}"] = items + prexian_config["num_all_#{index_name}"] = items.size + end + + # Common method to get projects from a site + def get_projects(site) + projects = site.collections['projects'].docs.select do |item| + pieces = item.url.split('/') + pieces.length == 4 && pieces[-1] == 'index' && pieces[1] == 'projects' + end + + # Add project name (matches directory name, may differ from title) + projects.map do |project| + project.data['name'] = project.url.split('/')[2] + project + end + end + end + + # Configuration for different types of filterable indexes + class IndexConfig + INDEXES = { + 'software' => { + item_test: ->(item) { item.path.include? '/_software' and !item.path.include? '/docs' } + }, + 'specs' => { + item_test: ->(item) { item.path.include? '/_specs' and !item.path.include? '/docs' } + } + }.freeze + + def self.get_config(index_name) + INDEXES[index_name] + end + + def self.all_indexes + INDEXES + end + end + + # Filtered index page for tag-based filtering + class FilteredIndexPage < ::Jekyll::Page + def initialize(site, base, dir, tag, items, index_page) + @site = site + @base = base + @dir = dir + @name = 'index.html' + + process(@name) + read_yaml(File.join(base, '_pages'), "#{index_page}.html") + data['tag'] = tag + data['items'] = items + end + end +end diff --git a/lib/rop/png_diagram.html b/lib/prexian/png_diagram.html similarity index 100% rename from lib/rop/png_diagram.html rename to lib/prexian/png_diagram.html diff --git a/lib/rop/png_diagram_page.rb b/lib/prexian/png_diagram_page.rb similarity index 98% rename from lib/rop/png_diagram_page.rb rename to lib/prexian/png_diagram_page.rb index 5b394d4..45e146a 100644 --- a/lib/rop/png_diagram_page.rb +++ b/lib/prexian/png_diagram_page.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Rop +module Prexian class PngDiagramPage < ::Jekyll::Page EXTRA_STYLESHEETS = [{ 'href' => 'https://unpkg.com/leaflet@1.3.4/dist/leaflet.css', diff --git a/lib/prexian/project_reader.rb b/lib/prexian/project_reader.rb new file mode 100644 index 0000000..05c7717 --- /dev/null +++ b/lib/prexian/project_reader.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'jekyll' +require_relative 'site_loader' +require_relative 'hub_site_loader' +require_relative 'project_site_loader' + +module Prexian + # Main entry point for reading project data + class ProjectReader < ::Jekyll::Reader + def initialize(site) + super + @config = @site.config + end + + def read + super + + prexian_config = @config['prexian'] || { 'site_type' => 'project' } + + @loader = case prexian_config['site_type'] + when 'hub' + puts 'Prexian ProjectReader: Creating HubSiteLoader' + HubSiteLoader.new(@site) + when 'project' + puts 'Prexian ProjectReader: Creating ProjectSiteLoader' + ProjectSiteLoader.new(@site) + end + + @loader.read_content + end + end + +end diff --git a/lib/prexian/project_site_loader.rb b/lib/prexian/project_site_loader.rb new file mode 100644 index 0000000..962eb9f --- /dev/null +++ b/lib/prexian/project_site_loader.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative 'site_loader' + +module Prexian + # Handles reading content for individual project sites + # Inherits all base site functionality from SiteLoader + class ProjectSiteLoader < SiteLoader + # Add methods expected by tests for backward compatibility + def fetch_and_read_software(collection_name) + @content_processor.process_collection(collection_name, SoftwareEntry, refresh_condition: refresh_condition) + end + + def fetch_and_read_specs(collection_name, build_pages: false) + @content_processor.process_collection(collection_name, SpecificationEntry, refresh_condition: refresh_condition) + end + end +end diff --git a/lib/prexian/site_loader.rb b/lib/prexian/site_loader.rb new file mode 100644 index 0000000..e38c5fd --- /dev/null +++ b/lib/prexian/site_loader.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require_relative 'git_service' +require_relative 'configuration_helper' +require_relative 'git_content_processor' +require_relative 'content_entry' + +module Prexian + # Base class for reading content for sites (both project and hub sites) + class SiteLoader + include ConfigurationHelper + + def initialize(site, git_service: nil) + @site = site + @config = @site.config + @git_service = git_service || GitService.new + @content_processor = GitContentProcessor.new(@site, git_service: @git_service, site_loader: self) + end + + def read_content + fetch_hub_logo if is_project? + @content_processor.process_collection('software', SoftwareEntry, refresh_condition: refresh_condition) + @content_processor.process_collection('specs', SpecificationEntry, refresh_condition: refresh_condition) + end + + private + + def fetch_hub_logo + puts 'Prexian SiteLoader: Fetching hub data' + Jekyll.logger.debug('Prexian SiteLoader: Fetching hub data') + + # Check if hub configuration exists + hub_config = prexian_config['hub'] + unless hub_config + Jekyll.logger.warn('[WARNING] prexian.hub is required but not set, skipping hub data fetch') + return + end + + hub_repo_url = hub_config['git_repo_url'] + unless hub_repo_url + Jekyll.logger.warn('[WARNING] prexian.hub.git_repo_url is required but not set, skipping hub data fetch') + return + end + + hub_repo_branch = prexian_config['hub']['git_repo_branch'] || default_repo_branch + + puts "Prexian SiteLoader: Hub repository branch: #{hub_repo_branch}" + Jekyll.logger.debug("Prexian SiteLoader: Hub repository branch: #{hub_repo_branch}") + + begin + hub_git_config = { + repo_url: hub_repo_url, + branch: hub_repo_branch + } + + hub_destination = File.join(@site.source, '_hub-site') + hub_includes = File.join(hub_destination, 'hub') + + checkout_result = @git_service.checkout_and_copy_content( + hub_git_config, + hub_includes, + refresh_condition: refresh_condition + ) + + puts "Prexian SiteLoader: Checkout result: #{checkout_result.inspect}" + return unless File.directory?(hub_includes) + + puts "Prexian SiteLoader: Hub site copied to #{hub_includes}" + + add_to_includes_load_paths(hub_destination) + extract_hub_data(hub_includes) + rescue Prexian::GitService::GitError => e + Jekyll.logger.warn("Prexian SiteLoader: Failed to fetch hub data: #{e.message}") + end + end + + def extract_hub_data(hub_path) + hub_config_path = File.join(hub_path, '_config.yml') + return unless File.exist?(hub_config_path) + + begin + require 'yaml' + hub_config = YAML.load_file(hub_config_path) + + # Extract hub data and make it available to Jekyll + @site.data ||= {} + @site.data['hub'] = { + 'title' => hub_config.dig('prexian', 'title') || hub_config['title'], + 'description' => hub_config.dig('prexian', 'description') || hub_config['description'], + 'url' => hub_config['url'] + } + + puts "Prexian SiteLoader: Extracted hub data: #{@site.data['hub'].inspect}" + Jekyll.logger.debug("Prexian SiteLoader: Extracted hub data: #{@site.data['hub'].inspect}") + rescue => e + Jekyll.logger.warn("Prexian SiteLoader: Failed to extract hub data: #{e.message}") + end + end + + # Shared method to add directories to Jekyll's includes_load_paths + def add_to_includes_load_paths(directory_path) + return unless File.directory?(directory_path) + + Jekyll.logger.debug("Prexian SiteLoader: Adding #{directory_path} to includes_load_paths") + + # Add directory to Jekyll's includes_load_paths + unless @site.includes_load_paths.include?(directory_path) + + @site.includes_load_paths << directory_path + end + end + end +end diff --git a/lib/rop/spec_builder.rb b/lib/prexian/spec_builder.rb similarity index 94% rename from lib/rop/spec_builder.rb rename to lib/prexian/spec_builder.rb index d0f33a5..1ceff6c 100644 --- a/lib/rop/spec_builder.rb +++ b/lib/prexian/spec_builder.rb @@ -4,8 +4,16 @@ require 'fastimage' require_relative 'png_diagram_page' -module Rop +module Prexian class SpecBuilder + + class << self + def valid_engine?(engine) + # Check if the engine file exists and is a valid Ruby file + File.exist?(engine) && File.extname(engine) == '.rb' + end + end + attr_reader :built_pages def initialize(site, spec_index_doc, spec_source_base, spec_out_base, engine, opts) diff --git a/lib/rop/version.rb b/lib/prexian/version.rb similarity index 62% rename from lib/rop/version.rb rename to lib/prexian/version.rb index 9ba091f..c51f901 100644 --- a/lib/rop/version.rb +++ b/lib/prexian/version.rb @@ -1,3 +1,3 @@ -module Rop +module Prexian VERSION = '2.1.18' end diff --git a/lib/rop.rb b/lib/rop.rb deleted file mode 100644 index 344e61d..0000000 --- a/lib/rop.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative 'rop/site_type' -require_relative 'rop/project_reader' -require_relative 'rop/filterable_index' -require_relative 'rop/blog_index' -require_relative 'rop/spec_builder' diff --git a/lib/rop/_plugins b/lib/rop/_plugins deleted file mode 120000 index 2f9ceba..0000000 --- a/lib/rop/_plugins +++ /dev/null @@ -1 +0,0 @@ -_plugins/ \ No newline at end of file diff --git a/lib/rop/blog_index.rb b/lib/rop/blog_index.rb deleted file mode 100644 index aa1b1a0..0000000 --- a/lib/rop/blog_index.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -require 'digest/md5' - -module Rop - # - # Adds a variable holding the array of posts of open hub blog - # and from each individual project blog, combined and sorted by date. - # - # It also does some processing on the posts - # as required by the Open Project theme. - # - class CombinedPostArrayGenerator < ::Jekyll::Generator - safe true - - def generate(site) - site_posts = site.posts.docs - - if site.config['is_hub'] - # Get documents representing projects - projects = site.collections['projects'].docs.select do |item| - pieces = item.url.split('/') - pieces.length == 4 && pieces[-1] == 'index' && pieces[1] == 'projects' - end - # Add project name (matches directory name, may differ from title) - projects = projects.map do |project| - project.data['name'] = project.url.split('/')[2] - project - end - - # Get documents representnig posts from each project’s blog - project_posts = site.collections['projects'].docs.select { |item| item.url.include? '_posts' } - - # Add parent project’s data hash onto each - project_posts = project_posts.map do |post| - project_name = post.url.split('/')[2] - post.data['parent_project'] = projects.detect { |p| p.data['name'] == project_name } - post.content = '' - post - end - - posts_combined = (project_posts + site_posts) - - else - posts_combined = site_posts - - end - - # On each post, replace authors’ emails with corresponding md5 hashes - # suitable for hotlinking authors’ Gravatar profile pictures. - posts_combined = posts_combined.sort_by(&:date).reverse.map do |post| - process_author(post.data['author']) if post.data.key? 'author' - - if post.data.key? 'authors' - post.data['authors'].map do |author| - process_author(author) - end - end - - post - end - - # Make combined blog post array available site-wide - site.config['posts_combined'] = posts_combined - site.config['num_posts_combined'] = posts_combined.size - end - - private - - def process_author(author) - email = author['email'] - hash = Digest::MD5.hexdigest(email) - author['email'] = hash - author['plaintext_email'] = email - author - end - end -end diff --git a/lib/rop/filterable_index.rb b/lib/rop/filterable_index.rb deleted file mode 100644 index ae22b50..0000000 --- a/lib/rop/filterable_index.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -module Rop - # On an open hub site, Jekyll Open Project theme assumes the existence of two types - # of item indexes: software and specs, where items are gathered - # from across open projects in the hub. - # - # The need for :item_test arises from our data structure (see Jekyll Open Project theme docs) - # and the fact that Jekyll doesn’t intuitively handle nested collections. - INDEXES = { - 'software' => { - item_test: ->(item) { item.path.include? '/_software' and !item.path.include? '/docs' } - }, - 'specs' => { - item_test: ->(item) { item.path.include? '/_specs' and !item.path.include? '/docs' } - } - }.freeze - - # Below passes the `items` variable to normal (unfiltered) - # index page layout. - - class IndexPageGenerator < ::Jekyll::Generator - safe true - - def generate(site) - site.config['max_featured_software'] = 3 - site.config['max_featured_specs'] = 3 - site.config['max_featured_posts'] = 3 - - INDEXES.each do |index_name, params| - collection_name = if site.config['is_hub'] - 'projects' - else - index_name - end - - next unless site.collections.key? collection_name - - # Filters items from given collection_name through item_test function - # and makes items available in templates via e.g. site.all_specs, site.all_software - - items = get_all_items(site, collection_name, params[:item_test]) - - site.config["one_#{index_name}"] = items[0] if items.length == 1 - - site.config["all_#{index_name}"] = items - site.config["num_all_#{index_name}"] = items.size - - featured_items = items.reject { |item| item.data['feature_with_priority'].nil? } - site.config["featured_#{index_name}"] = featured_items.sort_by { |item| item.data['feature_with_priority'] } - site.config["num_featured_#{index_name}"] = featured_items.size - - non_featured_items = items.select { |item| item.data['feature_with_priority'].nil? } - site.config["non_featured_#{index_name}"] = non_featured_items - site.config["num_non_featured_#{index_name}"] = non_featured_items.size - end - end - - def get_all_items(site, collection_name, filter_func) - # Fetches items of specified type, ordered and prepared for usage in index templates - - collection = site.collections[collection_name] - - raise "Collection does not exist: #{collection_name}" if collection.nil? - - items = collection.docs.select do |item| - filter_func.call(item) - end - - default_time = Time.new(1989, 12, 31, 0, 0, 0, '+00:00') - - items.sort! do |i1, i2| - val1 = i1.data.fetch('last_update', default_time) || default_time - val2 = i2.data.fetch('last_update', default_time) || default_time - (val2 <=> val1) || 0 - end - - if site.config['is_hub'] - items.map! do |item| - project_name = item.url.split('/')[2] - project_path = "_projects/#{project_name}/index.md" - - item.data['project_name'] = project_name - item.data['project_data'] = site.collections['projects'].docs.select do |proj| - proj.path.end_with? project_path - end [0] - - item - end - end - - items - end - end - # Each software or spec item can have its tags, - # and the theme allows to filter each index by a tag. - # The below generates an additional index page - # for each tag in an index, like software/Ruby. - # - # Note: this expects "_pages/<index page>.html" to be present in site source, - # so it would fail if theme setup instructions were not followed fully. - - class FilteredIndexPage < ::Jekyll::Page - def initialize(site, base, dir, tag, items, index_page) - @site = site - @base = base - @dir = dir - @name = 'index.html' - - process(@name) - read_yaml(File.join(base, '_pages'), "#{index_page}.html") - data['tag'] = tag - data['items'] = items - end - end - - class FilteredIndexPageGenerator < IndexPageGenerator - safe true - - def generate(site) - INDEXES.each do |index_name, params| - collection_name = if site.config['is_hub'] - 'projects' - else - index_name - end - - items = get_all_items(site, collection_name, params[:item_test]) - - # Creates a data structure like { tag1: [item1, item2], tag2: [item2, item3] } - tags = {} - items.each do |item| - (item.data['tags'] or []).each do |tag| - tags[tag] = [] unless tags.key? tag - tags[tag].push(item) - end - end - - # Creates a filtered index page for each tag - tags.each do |tag, tagged_items| - site.pages << FilteredIndexPage.new( - site, - site.source, - # The filtered page will be nested under /<index page>/<tag>.html - File.join(index_name, tag), - tag, - tagged_items, - index_name - ) - end - end - end - end -end diff --git a/lib/rop/project_reader.rb b/lib/rop/project_reader.rb deleted file mode 100644 index 203ee42..0000000 --- a/lib/rop/project_reader.rb +++ /dev/null @@ -1,373 +0,0 @@ -# frozen_string_literal: true - -require 'fileutils' -require 'git' -require 'jekyll-data/reader' - -module Rop - DEFAULT_DOCS_SUBTREE = 'docs' - DEFAULT_REPO_REMOTE_NAME = 'origin' - DEFAULT_REPO_BRANCH = 'main' - # Can be overridden by default_repo_branch in site config. - # Used by shallow_git_checkout. - - class NonLiquidDocument < ::Jekyll::Document - def render_with_liquid? - false - end - end - - class CollectionDocReader < ::Jekyll::DataReader - def read(dir, collection) - read_project_subdir(dir, collection) - end - - def read_project_subdir(dir, collection, nested: false) - return unless File.directory?(dir) && !@entry_filter.symlink?(dir) - - entries = Dir.chdir(dir) do - Dir['*.{adoc,md,markdown,html,svg,png}'] + Dir['*'].select { |fn| File.directory?(fn) } - end - - entries.each do |entry| - path = File.join(dir, entry) - - Jekyll.logger.debug('OPF:', "Reading entry #{path}") - - if File.directory?(path) - read_project_subdir(path, collection, nested: true) - - elsif nested || (File.basename(entry, '.*') != 'index') - ext = File.extname(path) - if ['.adoc', '.md', '.markdown'].include? ext - doc = NonLiquidDocument.new(path, site: @site, collection: collection) - doc.read - - # Add document to Jekyll document database if it refers to software or spec - # (as opposed to be some random nested document within repository source, like a README) - doc_url_parts = doc.url.split('/') - Jekyll.logger.debug('OPF:', - "Reading document in collection #{collection.label} with URL #{doc.url} (#{doc_url_parts.size} parts)") - if (collection.label != 'projects') || (doc_url_parts.size == 5) - Jekyll.logger.debug('OPF:', "Adding document with URL: #{doc.url}") - collection.docs << doc - else - Jekyll.logger.debug('OPF:', - "Did NOT add document with URL (possibly nesting level doesn’t match): #{doc.url}") - end - else - Jekyll.logger.debug('OPF:', "Adding static file: #{path}") - collection.files << ::Jekyll::StaticFile.new( - @site, - @site.source, - Pathname.new(File.dirname(path)).relative_path_from(Pathname.new(@site.source)).to_s, - File.basename(path), - collection - ) - end - end - end - end - end - - # - # Below deals with fetching each open project’s data from its site’s repo - # (such as posts, template includes, software and specs) - # and reading it into 'projects' collection docs. - # - - class ProjectReader < ::JekyllData::Reader - # TODO: Switch to @site.config? - @@siteconfig = Jekyll.configuration({}) - - def read - super - if @site.config['is_hub'] - fetch_and_read_projects - else - fetch_and_read_software('software') - fetch_and_read_specs('specs', build_pages: true) - fetch_hub_logo - end - end - - private - - def fetch_hub_logo - return unless @site.config.key?('parent_hub') && @site.config['parent_hub'].key?('git_repo_url') - - git_shallow_checkout( - File.join(@site.source, 'parent-hub'), - @site.config['parent_hub']['git_repo_url'], - ['assets', 'title.html'], - @site.config['parent_hub']['git_repo_branch'] - ) - end - - def fetch_and_read_projects - project_indexes = @site.collections['projects'].docs.select do |doc| - pieces = doc.id.split('/') - pieces.length == 4 and pieces[1] == 'projects' and pieces[3] == 'index' - end - project_indexes.each do |project| - project_path = project.path.split('/')[0..-2].join('/') - - git_shallow_checkout( - project_path, - project['site']['git_repo_url'], - %w[assets _posts _software _specs], - project['site']['git_repo_branch'] - ) - - Jekyll.logger.debug('OPF:', "Reading files in project #{project_path}") - - CollectionDocReader.new(site).read( - project_path, - @site.collections['projects'] - ) - - fetch_and_read_software('projects') - fetch_and_read_specs('projects') - end - end - - def build_and_read_spec_pages(collection_name, index_doc, build_pages: false) - spec_config = extract_spec_config(index_doc) - repo_checkout = git_shallow_checkout( - spec_config[:checkout_path], - spec_config[:repo_url], - [spec_config[:repo_subtree]], - spec_config[:repo_branch] - ) - - return unless repo_checkout[:success] - - build_spec_pages(collection_name, index_doc, spec_config) if build_pages - - index_doc.merge_data!({ 'last_update' => repo_checkout[:modified_at] }) - end - - def extract_spec_config(index_doc) - item_name = index_doc.id.split('/')[-1] - src = index_doc.data['spec_source'] - build = src['build'] - - spec_checkout_path = "#{index_doc.path.split('/')[0..-2].join('/')}/#{item_name}" - spec_root = src['git_repo_subtree'] ? "#{spec_checkout_path}/#{src['git_repo_subtree']}" : spec_checkout_path - - { - item_name: item_name, - repo_url: src['git_repo_url'], - repo_subtree: src['git_repo_subtree'], - repo_branch: src['git_repo_branch'], - engine: build['engine'], - engine_opts: build['options'] || {}, - checkout_path: spec_checkout_path, - spec_root: spec_root - } - end - - def build_spec_pages(collection_name, index_doc, spec_config) - builder = Rop::SpecBuilder.new( - @site, - index_doc, - spec_config[:spec_root], - "specs/#{spec_config[:item_name]}", - spec_config[:engine], - spec_config[:engine_opts] - ) - - builder.build - builder.built_pages.each do |page| - @site.pages << page - end - - CollectionDocReader.new(site).read( - spec_config[:checkout_path], - @site.collections[collection_name] - ) - end - - def fetch_and_read_specs(collection_name, build_pages: false) - # collection_name would be either specs or (for hub site) projects - - Jekyll.logger.debug('OPF:', "Fetching specs for items in collection #{collection_name} (if it exists)") - - return unless @site.collections.key?(collection_name) - - Jekyll.logger.debug('OPF:', "Fetching specs for items in collection #{collection_name}") - - # Get spec entry points - entry_points = @site.collections[collection_name].docs.select do |doc| - doc.data['spec_source'] - end - - if entry_points.empty? - Jekyll.logger.info('OPF:', - "Fetching specs for items in collection #{collection_name}: No entry points") - end - - entry_points.each do |index_doc| - Jekyll.logger.debug('OPF:', "Fetching specs: entry point #{index_doc.id} in collection #{collection_name}") - build_and_read_spec_pages(collection_name, index_doc, build_pages: build_pages) - end - end - - def fetch_and_read_software(collection_name) - # collection_name would be either software or (for hub site) projects - - Jekyll.logger.debug('OPF:', "Fetching software for items in collection #{collection_name} (if it exists)") - - return unless @site.collections.key?(collection_name) - - Jekyll.logger.debug('OPF:', "Fetching software for items in collection #{collection_name}") - - entry_points = @site.collections[collection_name].docs.select do |doc| - doc.data['repo_url'] - end - - if entry_points.empty? - Jekyll.logger.info('OPF:', - "Fetching software for items in collection #{collection_name}: No entry points") - end - - entry_points.each do |index_doc| - item_name = index_doc.id.split('/')[-1] - Jekyll.logger.debug('OPF:', "Fetching software: entry point #{index_doc.id} in collection #{collection_name}") - - docs = index_doc.data['docs'] - main_repo = index_doc.data['repo_url'] - main_repo_branch = index_doc.data['repo_branch'] - - sw_docs_repo = (docs['git_repo_url'] if docs) || main_repo - sw_docs_subtree = (docs['git_repo_subtree'] if docs) || DEFAULT_DOCS_SUBTREE - sw_docs_branch = (docs['git_repo_branch'] if docs) || main_repo_branch - - docs_path = "#{index_doc.path.split('/')[0..-2].join('/')}/#{item_name}" - - sw_docs_checkout = git_shallow_checkout(docs_path, sw_docs_repo, [sw_docs_subtree], sw_docs_branch) - - if sw_docs_checkout[:success] - CollectionDocReader.new(site).read( - docs_path, - @site.collections[collection_name] - ) - end - - # Get last repository modification timestamp. - # Fetch the repository for that purpose, - # unless it’s the same as the repo where docs are. - if !sw_docs_checkout[:success] || (sw_docs_repo != main_repo) - repo_path = "#{index_doc.path.split('/')[0..-2].join('/')}/_#{item_name}_repo" - repo_checkout = git_shallow_checkout(repo_path, main_repo, [], main_repo_branch) - index_doc.merge_data!({ 'last_update' => repo_checkout[:modified_at] }) - else - index_doc.merge_data!({ 'last_update' => sw_docs_checkout[:modified_at] }) - end - end - end - - def git_shallow_checkout(repo_path, remote_url, sparse_subtrees, branch_name) - # Returns hash with timestamp of latest repo commit - # and boolean signifying whether new repo has been initialized - # in the process of pulling the data. - - newly_initialized = false - repo = nil - - git_dir = File.join(repo_path, '.git') - git_info_dir = File.join(git_dir, 'info') - git_sparse_checkout_file = File.join(git_dir, 'info', 'sparse-checkout') - if File.exist? git_dir - repo = Git.open(repo_path) - - else - newly_initialized = true - - repo = Git.init(repo_path) - - repo.config( - 'core.sshCommand', - 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' - ) - - repo.add_remote(DEFAULT_REPO_REMOTE_NAME, remote_url) - - if sparse_subtrees.size.positive? - repo.config('core.sparseCheckout', true) - - FileUtils.mkdir_p git_info_dir - File.open(git_sparse_checkout_file, 'a') do |f| - sparse_subtrees.each { |path| f << "#{path}\n" } - end - end - - end - - refresh_condition = @@siteconfig['refresh_remote_data'] || 'last-resort' - repo_branch = branch_name || @@siteconfig['default_repo_branch'] || DEFAULT_REPO_BRANCH - - raise 'Invalid refresh_remote_data value in site’s _config.yml!' unless %w[always last-resort - skip].include?(refresh_condition) - - if refresh_condition == 'always' - repo.fetch(DEFAULT_REPO_REMOTE_NAME, { depth: 1 }) - repo.reset_hard - repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{repo_branch}", { f: true }) - - elsif refresh_condition == 'last-resort' - # This is the default case. - - begin - # Let’s try in case this repo has been fetched before (this would never be the case on CI though) - repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{repo_branch}", { f: true }) - rescue StandardError => e - if is_sparse_checkout_error(e, sparse_subtrees) - # Silence errors caused by nonexistent sparse checkout directories - return { - success: false, - newly_initialized: nil, - modified_at: nil - } - else - # In case of any other error, presume repo has not been fetched and do that now. - Jekyll.logger.debug('OPF:', "Fetching & checking out #{remote_url} for #{repo_path}") - repo.fetch(DEFAULT_REPO_REMOTE_NAME, { depth: 1 }) - begin - # Try checkout again - repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{repo_branch}", { f: true }) - rescue StandardError => e - raise e unless is_sparse_checkout_error(e, sparse_subtrees) - - # Again, silence an error caused by nonexistent sparse checkout directories… - return { - success: false, - newly_initialized: nil, - modified_at: nil - } - - # but this time throw any other error. - end - end - end - end - - latest_commit = repo.gcommit('HEAD') - - { - success: true, - newly_initialized: newly_initialized, - modified_at: latest_commit.date - } - end - - def is_sparse_checkout_error(err, subtrees) - if err.message.include? 'Sparse checkout leaves no entry on working directory' - Jekyll.logger.debug('OPF: It looks like sparse checkout of these directories failed:', subtrees.to_s) - true - else - false - end - end - end -end diff --git a/lib/rop/site_type.rb b/lib/rop/site_type.rb deleted file mode 100644 index 3812a3c..0000000 --- a/lib/rop/site_type.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Rop - # - # Infers from available content whether the site is a hub - # or individual project site, and adds site-wide config variable - # accessible as {{ site.is_hub }} in Liquid. - # - class SiteTypeVariableGenerator < Jekyll::Generator - def generate(site) - site.config['is_hub'] = hub_site?(site) - end - - private - - # If there’re projects defined, we assume it is indeed - # a Jekyll Open Project hub site. - def hub_site?(site) - projects = site.collections['projects'] - projects&.docs&.any? - end - end -end diff --git a/prexian.gemspec b/prexian.gemspec new file mode 100644 index 0000000..bd83c50 --- /dev/null +++ b/prexian.gemspec @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require_relative 'lib/prexian/version' + +Gem::Specification.new do |spec| + spec.name = 'prexian' + spec.version = Prexian::VERSION + spec.authors = ['Ribose Inc.'] + spec.email = ['open.source@ribose.com'] + + spec.summary = 'A Jekyll theme to manage a nexus of project sites.' + spec.homepage = 'https://github.com/riboseinc/prexian/' + spec.license = 'MIT' + + spec.metadata['plugin_type'] = 'theme' + + spec.files = `git ls-files -z`.split("\x0").select do |f| + f.match(%r{^(assets|exe|lib|_(includes|layouts|sass|data|pages)/|_config.yml|(LICENSE|README)((\.txt|\.md|\.markdown|\.adoc)|$))}i) + end + + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + # Core Jekyll dependencies + spec.add_runtime_dependency 'jekyll', '~> 4.4' + spec.add_runtime_dependency 'jekyll-seo-tag', '~> 2.1' + spec.add_runtime_dependency 'jekyll-sitemap', '~> 1.0' + + # Prexian-specific dependencies + spec.add_runtime_dependency 'fastimage', '~> 2.0' + spec.add_runtime_dependency 'git', '~> 1.0' + spec.add_runtime_dependency 'jekyll-asciidoc', '~> 3.0' + spec.add_runtime_dependency 'jekyll-data', '~> 1.0' + spec.add_runtime_dependency 'jekyll-redirect-from', '~> 0.16' + spec.add_runtime_dependency 'kramdown-parser-gfm', '~> 1.0' + spec.add_runtime_dependency 'kramdown-syntax-coderay', '~> 1.0' + spec.add_runtime_dependency 'thor', '~> 1.0' + + # Development dependencies + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'simplecov', '~> 0.21' +end diff --git a/script/bootstrap b/script/bootstrap new file mode 100755 index 0000000..492e553 --- /dev/null +++ b/script/bootstrap @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +gem install bundler +bundle install diff --git a/script/build b/script/build new file mode 100755 index 0000000..8b40780 --- /dev/null +++ b/script/build @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +echo "Building the example site..." +bundle exec jekyll build --config _config_prexian.yml diff --git a/script/build-hub b/script/build-hub new file mode 100755 index 0000000..15ac618 --- /dev/null +++ b/script/build-hub @@ -0,0 +1,5 @@ +#!/bin/sh + +cd spec/fixtures/hub +bundle +bundle exec jekyll build --config _config.yml --destination ../../_site_fixture_hub diff --git a/script/build-project b/script/build-project new file mode 100755 index 0000000..9e3921a --- /dev/null +++ b/script/build-project @@ -0,0 +1,5 @@ +#!/bin/sh + +cd spec/fixtures/project +bundle +bundle exec jekyll build --config _config.yml --destination ../../_site_fixture_project diff --git a/script/cibuild b/script/cibuild new file mode 100755 index 0000000..f5051bb --- /dev/null +++ b/script/cibuild @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +script/build + +if test -e "./_site/index.html";then + echo "It builds!" + rm -Rf _site +else + echo "Huh. That's odd. The example site doesn't seem to build." + exit 1 +fi + +gem build prexian.gemspec diff --git a/script/server b/script/server new file mode 100755 index 0000000..3cdbb8f --- /dev/null +++ b/script/server @@ -0,0 +1,3 @@ +#!/bin/sh + +bundle exec jekyll serve --config _config_prexian.yml $@ diff --git a/script/test-hub b/script/test-hub new file mode 100755 index 0000000..8f2ee93 --- /dev/null +++ b/script/test-hub @@ -0,0 +1,5 @@ +#!/bin/sh + +cd spec/fixtures/hub +bundle +bundle exec jekyll serve --config _config.yml $@ diff --git a/script/test-project b/script/test-project new file mode 100755 index 0000000..cc546ea --- /dev/null +++ b/script/test-project @@ -0,0 +1,5 @@ +#!/bin/sh + +cd spec/fixtures/project +bundle +bundle exec jekyll serve --config _config.yml $@ diff --git a/spec/fixtures/hub/Gemfile b/spec/fixtures/hub/Gemfile new file mode 100644 index 0000000..8735d86 --- /dev/null +++ b/spec/fixtures/hub/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'jekyll', '~> 4.3' +gem 'prexian', path: '../../..' diff --git a/spec/fixtures/hub/_config.yml b/spec/fixtures/hub/_config.yml new file mode 100644 index 0000000..693f695 --- /dev/null +++ b/spec/fixtures/hub/_config.yml @@ -0,0 +1,104 @@ +# Hub site configuration for testing +title: "TechHub Central" +description: "A central hub for open source technology projects" +url: "https://techhub.example.com" +baseurl: "" + +# Collections configuration +collections: + pages: + output: true + permalink: /:name/ + projects: + output: true + permalink: /projects/:name/ + software: + output: true + permalink: /:collection/:name/ + specs: + output: true + permalink: /:collection/:name/ + posts: + output: true + permalink: /blog/:year/:month/:day/:title/ + +# Theme configuration +theme: prexian + +# Prexian-specific configuration +prexian: + title: "TechHub Central" + description: "A central hub for open source technology projects" + tagline: "Connecting innovation across projects" + + # Hub site marker + site_type: hub + + # Git repository settings + default_repo_branch: main + refresh_remote_data: last-resort + + # Tag namespaces for categorization + tag_namespaces: + software: + writtenin: "Written in" + audience: "Audience" + interface: "Interface" + specs: + audience: "Audience" + status: "Status" + type: "Type" + + # Landing page priority + landing_priority: + - software + - specs + - blog + +# Build settings +markdown: kramdown +highlighter: rouge +kramdown: + input: GFM + syntax_highlighter: rouge + +# Plugins +plugins: + - prexian + +# Exclude from processing +exclude: + - Gemfile + - Gemfile.lock + - node_modules + - vendor/bundle/ + - vendor/cache/ + - vendor/gems/ + - vendor/ruby/ + - .git/ + - .github/ + +defaults: + - scope: + path: "assets/img" + values: + image: true + - scope: + path: "" + values: + layout: default + - scope: + path: _posts + type: posts + values: + layout: post + - scope: + path: _software + type: software + values: + layout: product + - scope: + path: _specs + type: specs + values: + layout: spec diff --git a/spec/fixtures/hub/_pages/blog.html b/spec/fixtures/hub/_pages/blog.html new file mode 100644 index 0000000..09bf0b3 --- /dev/null +++ b/spec/fixtures/hub/_pages/blog.html @@ -0,0 +1,6 @@ +--- +title: Blog +description: Latest updates and news from our projects. +layout: blog-index +hero_include: index-page-hero.html +--- diff --git a/spec/fixtures/hub/_pages/projects.html b/spec/fixtures/hub/_pages/projects.html new file mode 100644 index 0000000..c27d3d8 --- /dev/null +++ b/spec/fixtures/hub/_pages/projects.html @@ -0,0 +1,6 @@ +--- +title: Projects +description: Open source projects in our ecosystem. +layout: project-index +hero_include: index-page-hero.html +--- diff --git a/spec/fixtures/hub/_pages/software.html b/spec/fixtures/hub/_pages/software.html new file mode 100644 index 0000000..e8c4a4b --- /dev/null +++ b/spec/fixtures/hub/_pages/software.html @@ -0,0 +1,6 @@ +--- +title: Software +description: Software tools and libraries from our projects. +layout: software-index +hero_include: index-page-hero.html +--- diff --git a/spec/fixtures/hub/_pages/specs.html b/spec/fixtures/hub/_pages/specs.html new file mode 100644 index 0000000..840e8f6 --- /dev/null +++ b/spec/fixtures/hub/_pages/specs.html @@ -0,0 +1,6 @@ +--- +title: Specifications +description: Standards at the foundation of our projects. +layout: spec-index +hero_include: index-page-hero.html +--- diff --git a/spec/fixtures/hub/_posts/2024-01-15-introducing-techhub-central.md b/spec/fixtures/hub/_posts/2024-01-15-introducing-techhub-central.md new file mode 100644 index 0000000..746d70b --- /dev/null +++ b/spec/fixtures/hub/_posts/2024-01-15-introducing-techhub-central.md @@ -0,0 +1,39 @@ +--- +layout: post +title: "Introducing TechHub Central" +date: 2024-01-15 10:00:00 +0000 +categories: [announcement, platform] +tags: [launch, community, open-source] +author: "TechHub Team" +--- + +# Introducing TechHub Central + +We're excited to announce the launch of TechHub Central, a new platform designed to bring together innovative open source technology projects under one unified ecosystem. + +## Our Vision + +TechHub Central serves as the central hub for discovering, contributing to, and collaborating on cutting-edge technology projects. We believe that the future of technology lies in open collaboration and shared innovation. + +## What You'll Find Here + +- **Project Discovery**: Explore our growing collection of open source projects +- **Documentation**: Comprehensive guides and API references +- **Community**: Connect with developers and contributors worldwide +- **Resources**: Tools, templates, and best practices + +## Featured Projects + +Our initial launch includes two flagship projects: + +### DataForge +A comprehensive data processing and transformation toolkit that handles multiple formats and provides powerful validation capabilities. + +### DocFlow +A document processing and workflow automation platform that streamlines technical documentation processes. + +## Get Involved + +We invite you to explore our projects, contribute code, report issues, and help us build the future of open source technology. + +Welcome to TechHub Central! diff --git a/spec/fixtures/hub/_projects/dataforge.md b/spec/fixtures/hub/_projects/dataforge.md new file mode 100644 index 0000000..b26fa9b --- /dev/null +++ b/spec/fixtures/hub/_projects/dataforge.md @@ -0,0 +1,24 @@ +--- +title: "DataForge" +description: "Advanced data processing and transformation toolkit" +featured: true +home_url: "https://dataforge.example.com" +site: + local_path: "../project" +--- + +# DataForge + +DataForge is a comprehensive data processing and transformation toolkit designed for modern data workflows. It provides powerful APIs for data validation, transformation, and integration across multiple formats and sources. + +## Key Features + +- Multi-format data processing (JSON, XML, CSV, YAML) +- Schema validation and transformation +- Real-time data streaming capabilities +- Extensible plugin architecture +- Cloud-native deployment support + +## Getting Started + +Visit the [DataForge project site](https://dataforge.example.com) for comprehensive documentation and tutorials. diff --git a/spec/fixtures/hub/_projects/docflow.md b/spec/fixtures/hub/_projects/docflow.md new file mode 100644 index 0000000..e403e5b --- /dev/null +++ b/spec/fixtures/hub/_projects/docflow.md @@ -0,0 +1,32 @@ +--- +title: "DocFlow" +description: "Document processing and workflow automation platform" +featured: true +home_url: "https://docflow.example.com" +site: + local_path: "../docflow" +--- + +# DocFlow + +DocFlow is a powerful document processing and workflow automation platform that streamlines document creation, review, and publication processes for technical teams. + +## Key Features + +- Automated document generation from templates +- Multi-format output (PDF, HTML, DOCX, etc.) +- Collaborative review workflows +- Version control integration +- Custom styling and branding + +## Use Cases + +- Technical documentation +- Report generation +- Policy and procedure documents +- Academic publications +- Legal document processing + +## Getting Started + +Visit the [DocFlow project site](https://docflow.example.com) for installation guides and examples. diff --git a/spec/fixtures/hub/assets/img/symbol.svg b/spec/fixtures/hub/assets/img/symbol.svg new file mode 100644 index 0000000..6e287cf --- /dev/null +++ b/spec/fixtures/hub/assets/img/symbol.svg @@ -0,0 +1,26 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"> + <defs> + <linearGradient id="hubGradient" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" style="stop-color:#4A90E2;stop-opacity:1" /> + <stop offset="100%" style="stop-color:#357ABD;stop-opacity:1" /> + </linearGradient> + </defs> + + <!-- Hub center circle --> + <circle cx="50" cy="50" r="12" fill="url(#hubGradient)" stroke="#2C5282" stroke-width="2"/> + + <!-- Connected nodes --> + <circle cx="20" cy="20" r="8" fill="#E2E8F0" stroke="#4A90E2" stroke-width="2"/> + <circle cx="80" cy="20" r="8" fill="#E2E8F0" stroke="#4A90E2" stroke-width="2"/> + <circle cx="20" cy="80" r="8" fill="#E2E8F0" stroke="#4A90E2" stroke-width="2"/> + <circle cx="80" cy="80" r="8" fill="#E2E8F0" stroke="#4A90E2" stroke-width="2"/> + + <!-- Connection lines --> + <line x1="50" y1="50" x2="20" y2="20" stroke="#4A90E2" stroke-width="2" opacity="0.7"/> + <line x1="50" y1="50" x2="80" y2="20" stroke="#4A90E2" stroke-width="2" opacity="0.7"/> + <line x1="50" y1="50" x2="20" y2="80" stroke="#4A90E2" stroke-width="2" opacity="0.7"/> + <line x1="50" y1="50" x2="80" y2="80" stroke="#4A90E2" stroke-width="2" opacity="0.7"/> + + <!-- Text --> + <text x="50" y="95" text-anchor="middle" font-family="Arial, sans-serif" font-size="8" fill="#2C5282">HUB</text> +</svg> diff --git a/spec/fixtures/hub/index.md b/spec/fixtures/hub/index.md new file mode 100644 index 0000000..e4ef264 --- /dev/null +++ b/spec/fixtures/hub/index.md @@ -0,0 +1,17 @@ +--- +layout: home +title: "TechHub Central" +description: "Your gateway to innovative open source technology projects" +--- + +# Welcome to TechHub Central + +TechHub Central is the premier destination for discovering and contributing to cutting-edge open source technology projects. We bring together developers, researchers, and innovators from around the world to collaborate on solutions that shape the future. + +## Our Mission + +We believe in the power of open collaboration to solve complex technological challenges. Our platform connects project teams, provides resources, and fosters innovation across multiple domains including data standards, document processing, and developer tools. + +## Featured Projects + +Explore our ecosystem of interconnected projects, each contributing unique capabilities to the broader technology landscape. diff --git a/spec/fixtures/hub/title.html b/spec/fixtures/hub/title.html new file mode 100644 index 0000000..2ed87ab --- /dev/null +++ b/spec/fixtures/hub/title.html @@ -0,0 +1 @@ +a TechHub Central product. \ No newline at end of file diff --git a/spec/fixtures/project/Gemfile b/spec/fixtures/project/Gemfile new file mode 100644 index 0000000..8735d86 --- /dev/null +++ b/spec/fixtures/project/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'jekyll', '~> 4.3' +gem 'prexian', path: '../../..' diff --git a/spec/fixtures/project/_config.yml b/spec/fixtures/project/_config.yml new file mode 100644 index 0000000..4968f34 --- /dev/null +++ b/spec/fixtures/project/_config.yml @@ -0,0 +1,107 @@ +# Project site configuration for testing +title: "DataForge" +description: "Advanced data processing and transformation toolkit" +url: "https://dataforge.example.com" +# baseurl: "" + +# Collections configuration +collections: + pages: + output: true + permalink: /:name/ + software: + output: true + permalink: /:collection/:name/ + specs: + output: true + permalink: /:collection/:name/ + posts: + output: true + permalink: /blog/:year/:month/:day/:title/ + +# Theme configuration +theme: prexian + +# Prexian-specific configuration +prexian: + title: "DataForge" + description: "Advanced data processing and transformation toolkit" + tagline: "Transforming data with precision and power" + + # Project site marker (not a hub) + site_type: project + + # Hub configuration - pointing to local hub fixture + hub: + git_repo_url: "../hub" + git_repo_branch: "main" + home_url: "https://techhub.example.com/" + + # Git repository settings + default_repo_branch: main + refresh_remote_data: last-resort + + # Tag namespaces for categorization + tag_namespaces: + software: + writtenin: "Written in" + audience: "Audience" + interface: "Interface" + specs: + audience: "Audience" + status: "Status" + type: "Type" + + # Landing page priority + landing_priority: + - software + - specs + - blog + +# Build settings +markdown: kramdown +highlighter: rouge +kramdown: + input: GFM + syntax_highlighter: rouge + +# Plugins +plugins: + - prexian + +# Exclude from processing +exclude: + - Gemfile + - Gemfile.lock + - node_modules + - vendor/bundle/ + - vendor/cache/ + - vendor/gems/ + - vendor/ruby/ + - .git/ + - .github/ + +defaults: + - scope: + path: "assets/img" + values: + image: true + - scope: + path: "" + values: + layout: default + - scope: + path: _posts + type: posts + values: + layout: post + - scope: + path: _software + type: software + values: + layout: product + - scope: + path: _specs + type: specs + values: + layout: spec diff --git a/spec/fixtures/project/_pages/blog.html b/spec/fixtures/project/_pages/blog.html new file mode 100644 index 0000000..4a6625b --- /dev/null +++ b/spec/fixtures/project/_pages/blog.html @@ -0,0 +1,6 @@ +--- +title: Blog +description: Latest updates and news from this project. +layout: blog-index +hero_include: index-page-hero.html +--- diff --git a/spec/fixtures/project/_pages/software.html b/spec/fixtures/project/_pages/software.html new file mode 100644 index 0000000..1a5a23c --- /dev/null +++ b/spec/fixtures/project/_pages/software.html @@ -0,0 +1,6 @@ +--- +title: Software +description: Software tools and libraries from this project. +layout: software-index +hero_include: index-page-hero.html +--- diff --git a/spec/fixtures/project/_pages/specs.html b/spec/fixtures/project/_pages/specs.html new file mode 100644 index 0000000..5d07003 --- /dev/null +++ b/spec/fixtures/project/_pages/specs.html @@ -0,0 +1,6 @@ +--- +title: Specifications +description: Standards at the foundation of this project. +layout: spec-index +hero_include: index-page-hero.html +--- diff --git a/spec/fixtures/project/_posts/2024-02-01-dataforge-v2-release.md b/spec/fixtures/project/_posts/2024-02-01-dataforge-v2-release.md new file mode 100644 index 0000000..b75917a --- /dev/null +++ b/spec/fixtures/project/_posts/2024-02-01-dataforge-v2-release.md @@ -0,0 +1,94 @@ +--- +layout: post +title: "DataForge v2.0 Released" +date: 2024-02-01 09:00:00 +0000 +categories: [release, announcement] +tags: [v2.0, features, performance] +author: "DataForge Team" +--- + +# DataForge v2.0 Released + +We're excited to announce the release of DataForge v2.0, our most significant update yet! This major release brings substantial performance improvements, new features, and enhanced developer experience. + +## What's New in v2.0 + +### Performance Improvements +- **50% faster processing** for large datasets +- **Reduced memory footprint** by 30% +- **Optimized streaming** for real-time data processing +- **Parallel processing** support for multi-core systems + +### New Features + +#### Enhanced Schema Validation +- Support for complex nested schemas +- Custom validation functions +- Real-time validation feedback +- Schema inheritance and composition + +#### Improved CLI Experience +- Interactive mode for guided processing +- Better error messages and debugging +- Auto-completion for commands +- Configuration file support + +#### Extended Format Support +- Apache Parquet support +- Apache Avro integration +- Protocol Buffers compatibility +- Custom format plugins + +### Developer Experience + +#### Better Documentation +- Comprehensive API reference +- Step-by-step tutorials +- Real-world examples +- Video guides + +#### Enhanced Testing +- Built-in test framework +- Schema testing utilities +- Performance benchmarking tools +- Mock data generators + +## Migration Guide + +Upgrading from v1.x to v2.0 is straightforward: + +```bash +# Update your installation +gem update dataforge-cli + +# Check compatibility +dataforge migrate --check + +# Run migration if needed +dataforge migrate --from v1 --to v2 +``` + +## Breaking Changes + +- Configuration file format has been updated +- Some CLI flags have been renamed for consistency +- Legacy format plugins need to be updated + +See our [migration guide](https://docs.dataforge.example.com/migration/v2) for detailed information. + +## Get Started + +Download DataForge v2.0 today and experience the next generation of data processing tools! + +```bash +gem install dataforge-cli +``` + +## Community + +Join our growing community: +- [GitHub Discussions](https://github.com/techhub/dataforge/discussions) +- [Discord Server](https://discord.gg/dataforge) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/dataforge) + +Thank you to all contributors who made this release possible! diff --git a/spec/fixtures/project/_software/dataforge-cli.md b/spec/fixtures/project/_software/dataforge-cli.md new file mode 100644 index 0000000..1153b89 --- /dev/null +++ b/spec/fixtures/project/_software/dataforge-cli.md @@ -0,0 +1,46 @@ +--- +title: "DataForge CLI" +description: "Command-line interface for DataForge data processing" +featured: true +repo_url: "https://github.com/octocat/Hello-World" +repo_branch: "master" +tags: + writtenin: ["Ruby", "Go"] + audience: ["Developers", "Data Engineers"] + interface: ["CLI", "API"] +--- + +# DataForge CLI + +The DataForge CLI provides a powerful command-line interface for data processing and transformation tasks. Built for developers and data engineers who need efficient, scriptable data processing capabilities. + +## Features + +- **Batch Processing**: Process large datasets efficiently +- **Format Conversion**: Convert between JSON, XML, CSV, YAML +- **Schema Validation**: Validate data against predefined schemas +- **Pipeline Support**: Chain multiple processing operations +- **Plugin Architecture**: Extend functionality with custom plugins + +## Installation + +```bash +gem install dataforge-cli +``` + +## Quick Start + +```bash +# Convert JSON to CSV +dataforge convert input.json --to csv --output data.csv + +# Validate data against schema +dataforge validate data.json --schema schema.json + +# Process data pipeline +dataforge pipeline --config pipeline.yml +``` + +## Documentation + +Visit our [documentation site](https://docs.dataforge.example.com/cli) for comprehensive guides and API reference. diff --git a/spec/fixtures/project/_specs/dataforge-schema-spec.md b/spec/fixtures/project/_specs/dataforge-schema-spec.md new file mode 100644 index 0000000..bff2763 --- /dev/null +++ b/spec/fixtures/project/_specs/dataforge-schema-spec.md @@ -0,0 +1,81 @@ +--- +title: "DataForge Schema Specification" +description: "Comprehensive schema specification for DataForge data structures" +featured: true +spec_source: + git_repo_url: "https://github.com/octocat/Hello-World" + git_repo_branch: "master" +tags: + audience: ["Developers", "Data Architects"] + status: ["Draft", "Under Review"] + type: ["Technical Specification", "API Documentation"] +--- + +# DataForge Schema Specification + +The DataForge Schema Specification defines a standardized format for describing data structures, validation rules, and transformation mappings within the DataForge ecosystem. + +## Overview + +This specification provides: + +- **Schema Definition Language**: JSON-based schema definition format +- **Validation Rules**: Comprehensive validation rule syntax +- **Type System**: Rich type system for data validation +- **Transformation Mappings**: Schema-to-schema transformation definitions + +## Schema Format + +### Basic Structure + +```json +{ + "$schema": "https://schemas.dataforge.example.com/v1/schema.json", + "id": "https://schemas.dataforge.example.com/user-profile.json", + "title": "User Profile Schema", + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "email": { + "type": "string", + "format": "email" + }, + "age": { + "type": "integer", + "minimum": 0, + "maximum": 150 + } + }, + "required": ["name", "email"] +} +``` + +### Validation Rules + +The specification supports various validation rules: + +- **Type Validation**: Basic type checking (string, number, boolean, etc.) +- **Format Validation**: Email, URL, date, time formats +- **Range Validation**: Min/max values for numbers and strings +- **Pattern Validation**: Regular expression matching +- **Custom Validation**: User-defined validation functions + +## Implementation + +Reference implementations are available for: + +- JavaScript/Node.js +- Python +- Ruby +- Go +- Java + +## Version History + +- **v1.0**: Initial specification release +- **v1.1**: Added transformation mappings +- **v1.2**: Enhanced validation rules and custom validators diff --git a/spec/fixtures/project/assets/img/symbol.svg b/spec/fixtures/project/assets/img/symbol.svg new file mode 100644 index 0000000..b792888 --- /dev/null +++ b/spec/fixtures/project/assets/img/symbol.svg @@ -0,0 +1,30 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> + <defs> + <linearGradient id="projectGradient" x1="0%" y1="0%" x2="100%" y2="100%"> + <stop offset="0%" style="stop-color:#10B981;stop-opacity:1" /> + <stop offset="100%" style="stop-color:#059669;stop-opacity:1" /> + </linearGradient> + </defs> + + <!-- Project box/package shape --> + <rect x="20" y="25" width="60" height="50" rx="8" ry="8" fill="url(#projectGradient)" stroke="#047857" stroke-width="2"/> + + <!-- Project icon/gear inside --> + <circle cx="50" cy="50" r="12" fill="none" stroke="#FFFFFF" stroke-width="2"/> + <circle cx="50" cy="50" r="3" fill="#FFFFFF"/> + + <!-- Gear teeth --> + <rect x="48" y="32" width="4" height="6" fill="#FFFFFF"/> + <rect x="48" y="62" width="4" height="6" fill="#FFFFFF"/> + <rect x="32" y="48" width="6" height="4" fill="#FFFFFF"/> + <rect x="62" y="48" width="6" height="4" fill="#FFFFFF"/> + + <!-- Corner indicators --> + <circle cx="30" cy="35" r="2" fill="#FFFFFF"/> + <circle cx="70" cy="35" r="2" fill="#FFFFFF"/> + <circle cx="30" cy="65" r="2" fill="#FFFFFF"/> + <circle cx="70" cy="65" r="2" fill="#FFFFFF"/> + + <!-- Text --> + <text x="50" y="90" text-anchor="middle" font-family="Arial, sans-serif" font-size="8" fill="#047857">PROJECT</text> +</svg> diff --git a/spec/fixtures/project/index.md b/spec/fixtures/project/index.md new file mode 100644 index 0000000..ee0c225 --- /dev/null +++ b/spec/fixtures/project/index.md @@ -0,0 +1,39 @@ +--- +layout: home +title: "DataForge" +description: "Advanced data processing and transformation toolkit" +--- + +# DataForge + +Welcome to DataForge, the comprehensive data processing and transformation toolkit designed for modern data workflows. Our platform provides powerful APIs and tools for data validation, transformation, and integration across multiple formats and sources. + +## What is DataForge? + +DataForge is built to handle the complexities of modern data processing. Whether you're working with JSON, XML, CSV, or YAML, our toolkit provides the tools you need to validate, transform, and integrate your data seamlessly. + +## Key Features + +### Multi-Format Processing +- Support for JSON, XML, CSV, YAML, and more +- Automatic format detection and conversion +- Preserves data integrity during transformations + +### Schema Validation +- Built-in validation for common data formats +- Custom schema definition support +- Real-time validation feedback + +### Real-Time Streaming +- Process data streams in real-time +- Low-latency processing capabilities +- Scalable architecture for high-volume data + +### Extensible Architecture +- Plugin-based system for custom processors +- Easy integration with existing workflows +- API-first design for maximum flexibility + +## Getting Started + +Explore our tools and specifications to get started with DataForge in your projects. diff --git a/spec/integration/hub_site_spec.rb b/spec/integration/hub_site_spec.rb new file mode 100644 index 0000000..461c1ba --- /dev/null +++ b/spec/integration/hub_site_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Hub Site Integration', type: :integration do + let(:hub_fixture_path) { File.join(File.dirname(__FILE__), '..', 'fixtures', 'hub') } + let(:project_fixture_path) { File.join(File.dirname(__FILE__), '..', 'fixtures', 'project') } + + describe 'hub site functionality' do + let(:site) { build_site(hub_fixture_path) } + + it 'identifies itself as a hub site' do + prexian_config = site.config['prexian'] || {} + expect(prexian_config['site_type']).to eq('hub') + end + + it 'processes project repositories' do + # Create a real git service with a temporary cache directory + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + hub_reader = Prexian::HubSiteLoader.new(site, git_service: git_service) + + # This should process the projects defined in our fixture + expect { hub_reader.read_projects }.not_to raise_error + + # Verify that projects collection exists + expect(site.collections['projects']).to be_a(Jekyll::Collection) + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'aggregates content from multiple projects' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + hub_reader = Prexian::HubSiteLoader.new(site, git_service: git_service) + + # Process projects + hub_reader.read_projects + + # Check that we have the expected collections + expect(site.collections.keys).to include('projects', 'software', 'specs', 'posts') + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'handles project repository failures gracefully' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + # Create a site with invalid project repository URLs + invalid_config = site.config.dup + invalid_config['collections'] = { + 'projects' => { 'output' => true } + } + + invalid_site = Jekyll::Site.new(Jekyll::Configuration.from(invalid_config)) + invalid_site.collections['projects'] = Jekyll::Collection.new(invalid_site, 'projects') + + # Add a project with invalid repo URL + project_doc = Jekyll::Document.new( + File.join(hub_fixture_path, '_projects', 'invalid.md'), + site: invalid_site, + collection: invalid_site.collections['projects'] + ) + project_doc.data['site'] = { + 'git_repo_url' => 'https://invalid-repo-url-that-does-not-exist.com/repo.git', + 'git_repo_branch' => 'main' + } + invalid_site.collections['projects'].docs << project_doc + + hub_reader = Prexian::HubSiteLoader.new(invalid_site, git_service: git_service) + + # Should not raise error even with invalid repositories + expect { hub_reader.read_projects }.not_to raise_error + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'processes software entries from projects' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + hub_reader = Prexian::HubSiteLoader.new(site, git_service: git_service) + + # Process projects - this will attempt to read from our local project fixture + hub_reader.read_projects + + # Verify software collection exists + expect(site.collections['software']).to be_a(Jekyll::Collection) + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'processes spec entries from projects' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + hub_reader = Prexian::HubSiteLoader.new(site, git_service: git_service) + + # Process projects + hub_reader.read_projects + + # Verify specs collection exists + expect(site.collections['specs']).to be_a(Jekyll::Collection) + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + end + + describe 'configuration validation' do + let(:site) { build_site(hub_fixture_path) } + + it 'validates hub site configuration' do + prexian_config = site.config['prexian'] || {} + + expect(prexian_config['site_type']).to eq('hub') + expect(prexian_config['hub']).to be_nil + expect(prexian_config['tag_namespaces']).to be_a(Hash) + expect(prexian_config['landing_priority']).to be_an(Array) + end + + it 'processes tag namespaces correctly' do + prexian_config = site.config['prexian'] || {} + expect(prexian_config['tag_namespaces'].keys).to include('software', 'specs') + end + + it 'handles landing priority configuration' do + prexian_config = site.config['prexian'] || {} + expect(prexian_config['landing_priority']).to eq(%w[software specs blog]) + end + end +end diff --git a/spec/integration/project_site_spec.rb b/spec/integration/project_site_spec.rb new file mode 100644 index 0000000..f73b2a9 --- /dev/null +++ b/spec/integration/project_site_spec.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Project Site Integration', type: :integration do + let(:project_fixture_path) { File.join(File.dirname(__FILE__), '..', 'fixtures', 'project') } + let(:hub_fixture_path) { File.join(File.dirname(__FILE__), '..', 'fixtures', 'hub') } + + describe 'project site functionality' do + let(:site) { build_site(project_fixture_path) } + + it 'identifies itself as a project site' do + prexian_config = site.config['prexian'] || {} + expect(prexian_config['site_type']).to eq('project') + end + + it 'has parent hub configuration' do + prexian_config = site.config['prexian'] || {} + hub = prexian_config['hub'] || {} + expect(hub['git_repo_url']).to include('../hub') + expect(hub['home_url']).to eq('https://techhub.example.com/') + end + + it 'fetches hub logo from parent hub' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + project_reader = Prexian::ProjectSiteLoader.new(site, git_service: git_service) + + # This should attempt to fetch from the parent hub (our local fixture) + expect { project_reader.read_content }.not_to raise_error + + # Check that hub directory structure is created + hub_path = File.join(site.source, '_hub-site') + expect(Dir.exist?(hub_path) || Dir.exist?(File.join(hub_path, 'hub'))).to be_truthy + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + # Clean up hub directory + hub_path = File.join(site.source, 'hub') + FileUtils.rm_rf(hub_path) if Dir.exist?(hub_path) + end + end + + it 'processes software documentation' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + project_reader = Prexian::ProjectSiteLoader.new(site, git_service: git_service) + + # This should process the software defined in our fixture + expect { project_reader.read_content }.not_to raise_error + + # Verify software collection exists and has content + expect(site.collections['software']).to be_a(Jekyll::Collection) + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'processes specifications with page building' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + project_reader = Prexian::ProjectSiteLoader.new(site, git_service: git_service) + + # This should process the specs defined in our fixture + expect { project_reader.read_content }.not_to raise_error + + # Verify specs collection exists + expect(site.collections['specs']).to be_a(Jekyll::Collection) + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'handles repository checkout failures gracefully' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + # Create a site with invalid parent hub URL + invalid_config = site.config.dup + invalid_config['prexian']['hub']['git_repo_url'] = 'https://invalid-repo-url.com/repo.git' + + invalid_site = Jekyll::Site.new(Jekyll::Configuration.from(invalid_config)) + invalid_site.collections['software'] = Jekyll::Collection.new(invalid_site, 'software') + invalid_site.collections['specs'] = Jekyll::Collection.new(invalid_site, 'specs') + invalid_site.collections['posts'] = Jekyll::Collection.new(invalid_site, 'posts') + + project_reader = Prexian::ProjectSiteLoader.new(invalid_site, git_service: git_service) + + # Should not raise error even with invalid repositories + expect { project_reader.read_content }.not_to raise_error + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'updates document timestamps from repository' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + project_reader = Prexian::ProjectSiteLoader.new(site, git_service: git_service) + + # Process content + expect { project_reader.read_content }.not_to raise_error + + # Verify collections exist + expect(site.collections['software']).to be_a(Jekyll::Collection) + expect(site.collections['specs']).to be_a(Jekyll::Collection) + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + end + + describe 'configuration validation' do + let(:site) { build_site(project_fixture_path) } + + it 'validates project site configuration' do + prexian_config = site.config['prexian'] || {} + hub = prexian_config['hub'] || {} + + expect(prexian_config['site_type']).to eq('project') + expect(hub['git_repo_url']).to be_a(String) + expect(hub['git_repo_branch'] || prexian_config['default_repo_branch'] || 'main').to eq('main') + end + + it 'handles refresh conditions' do + prexian_config = site.config['prexian'] || {} + expect(prexian_config['refresh_remote_data'] || 'last-resort').to eq('last-resort') + end + + it 'processes tag namespaces' do + prexian_config = site.config['prexian'] || {} + expect(prexian_config['tag_namespaces'] || {}).to be_a(Hash) + end + end + + describe 'content processing' do + let(:site) { build_site(project_fixture_path) } + + it 'processes software with documentation repositories' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + project_reader = Prexian::ProjectSiteLoader.new(site, git_service: git_service) + + # This should process software from our fixture + expect { project_reader.send(:fetch_and_read_software, 'software') }.not_to raise_error + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'processes specifications with build configuration' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + project_reader = Prexian::ProjectSiteLoader.new(site, git_service: git_service) + + # This should process specs from our fixture + expect { project_reader.send(:fetch_and_read_specs, 'specs', build_pages: true) }.not_to raise_error + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + + it 'handles empty collections gracefully' do + cache_dir = Dir.mktmpdir('prexian_test_cache') + git_service = Prexian::GitService.new(cache_dir: cache_dir) + + begin + empty_site = build_site(project_fixture_path) + empty_site.collections['software'] = Jekyll::Collection.new(empty_site, 'software') + empty_site.collections['specs'] = Jekyll::Collection.new(empty_site, 'specs') + + project_reader = Prexian::ProjectSiteLoader.new(empty_site, git_service: git_service) + + expect { project_reader.read_content }.not_to raise_error + ensure + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + end + end +end diff --git a/spec/lib/prexian/cli_spec.rb b/spec/lib/prexian/cli_spec.rb new file mode 100644 index 0000000..a328506 --- /dev/null +++ b/spec/lib/prexian/cli_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'stringio' + +RSpec.describe Prexian::CLI do + let(:cli) { described_class.new } + let(:cache_dir) { Dir.mktmpdir('prexian_cli_test') } + + after do + FileUtils.rm_rf(cache_dir) if Dir.exist?(cache_dir) + end + + describe '#status' do + it 'displays cache statistics' do + # Set environment variable to use our test cache directory + ENV['ROP_CACHE_DIR'] = cache_dir + + # Create some test cache content + test_repo_dir = File.join(cache_dir, 'test_repo') + FileUtils.mkdir_p(test_repo_dir) + File.write(File.join(test_repo_dir, 'test_file.txt'), 'test content') + + expect { cli.status }.to output(/Cache Directory:/).to_stdout + expect { cli.status }.to output(/Total Repositories:/).to_stdout + expect { cli.status }.to output(/Total Size:/).to_stdout + ensure + ENV.delete('ROP_CACHE_DIR') + end + + it 'shows empty cache message when no repositories' do + # Set environment variable to use our test cache directory + ENV['ROP_CACHE_DIR'] = cache_dir + + expect { cli.status }.to output(/Cache is empty/).to_stdout + ensure + ENV.delete('ROP_CACHE_DIR') + end + end + + describe '#clean' do + context 'without specific repository' do + it 'cleans entire cache' do + # Set environment variable to use our test cache directory + ENV['ROP_CACHE_DIR'] = cache_dir + + # Create some test cache content + test_repo_dir = File.join(cache_dir, 'test_repo') + FileUtils.mkdir_p(test_repo_dir) + File.write(File.join(test_repo_dir, 'test_file.txt'), 'test content') + + expect { cli.clean }.to output(/Cleaned entire cache directory/).to_stdout + ensure + ENV.delete('ROP_CACHE_DIR') + end + end + + context 'with specific repository' do + it 'cleans specific repository cache' do + # Set environment variable to use our test cache directory + ENV['ROP_CACHE_DIR'] = cache_dir + + repo_url = 'https://github.com/example/repo.git' + allow(cli).to receive(:options).and_return({ project: repo_url }) + + expect { cli.clean }.to output(/Cleaned cache for project: #{Regexp.escape(repo_url)}/).to_stdout + ensure + ENV.delete('ROP_CACHE_DIR') + end + end + end + + describe '#version' do + it 'displays version information' do + expect { cli.version }.to output(/prexian version #{Prexian::VERSION}/).to_stdout + end + end + + describe 'private methods' do + describe '#format_bytes' do + it 'formats bytes correctly' do + expect(cli.send(:format_bytes, 1024)).to eq('1.00 KB') + expect(cli.send(:format_bytes, 1024 * 1024)).to eq('1.00 MB') + expect(cli.send(:format_bytes, 1024 * 1024 * 1024)).to eq('1.00 GB') + expect(cli.send(:format_bytes, 512)).to eq('512.00 B') + end + end + end +end diff --git a/spec/lib/prexian/git_service_spec.rb b/spec/lib/prexian/git_service_spec.rb new file mode 100644 index 0000000..a106a9b --- /dev/null +++ b/spec/lib/prexian/git_service_spec.rb @@ -0,0 +1,191 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'tmpdir' +require 'fileutils' + +RSpec.describe Prexian::GitService do + let(:temp_cache_dir) { Dir.mktmpdir('prexian_test_cache') } + let(:temp_dest_dir) { Dir.mktmpdir('prexian_test_dest') } + + after do + FileUtils.rm_rf(temp_cache_dir) if Dir.exist?(temp_cache_dir) + FileUtils.rm_rf(temp_dest_dir) if Dir.exist?(temp_dest_dir) + end + + describe '#initialize' do + it 'creates cache directory if it does not exist' do + non_existent_dir = File.join(temp_cache_dir, 'non_existent') + described_class.new(cache_dir: non_existent_dir) + + expect(Dir.exist?(non_existent_dir)).to be true + end + + it 'uses default cache directory when none provided' do + service = described_class.new + expected_default = File.expand_path('~/.prexian/cache/repos') + + expect(service.instance_variable_get(:@cache_dir)).to eq(expected_default) + end + + it 'uses environment variable for cache directory' do + ENV['ROP_CACHE_DIR'] = temp_cache_dir + service = described_class.new + + expect(service.instance_variable_get(:@cache_dir)).to eq(temp_cache_dir) + ensure + ENV.delete('ROP_CACHE_DIR') + end + end + + describe '#shallow_checkout' do + let(:service) { described_class.new(cache_dir: temp_cache_dir) } + let(:repo_url) { 'https://github.com/octocat/Hello-World' } + + it 'uses specified branch' do + branch = 'master' + result = service.shallow_checkout(repo_url, branch: branch) + + expect(result).to be_a(Hash) + expect(result).to have_key(:success) + expect(result).to have_key(:local_path) + expect(result[:local_path]).to include(branch) if result[:success] + end + + it 'handles sparse checkout subtrees' do + subtrees = %w[docs _posts] + branch = 'master' + result = service.shallow_checkout(repo_url, branch: branch, sparse_subtrees: subtrees) + + expect(result).to be_a(Hash) + expect(result).to have_key(:success) + expect(result).to have_key(:local_path) + end + + it 'handles git errors gracefully' do + invalid_repo_url = 'https://invalid-domain-that-does-not-exist.com/repo.git' + expect { + service.shallow_checkout(invalid_repo_url) + }.to raise_error(Prexian::GitService::GitError) + end + end + + describe '#copy_cached_content' do + let(:service) { described_class.new(cache_dir: temp_cache_dir) } + let(:source_dir) { File.join(temp_cache_dir, 'test_source') } + + before do + FileUtils.mkdir_p(source_dir) + FileUtils.mkdir_p(File.join(source_dir, 'docs')) + FileUtils.mkdir_p(File.join(source_dir, '_posts')) + File.write(File.join(source_dir, 'README.md'), 'Test content') + File.write(File.join(source_dir, 'docs', 'guide.md'), 'Guide content') + File.write(File.join(source_dir, '_posts', '2023-01-01-test.md'), 'Post content') + end + + it 'copies entire directory when no subtrees specified' do + result = service.copy_cached_content(source_dir, temp_dest_dir) + + expect(result).to be true + expect(File.exist?(File.join(temp_dest_dir, 'README.md'))).to be true + expect(File.exist?(File.join(temp_dest_dir, 'docs', 'guide.md'))).to be true + end + + it 'copies only specified subtrees' do + subtrees = ['docs'] + result = service.copy_cached_content(source_dir, temp_dest_dir, subtrees: subtrees) + + expect(result).to be true + expect(File.exist?(File.join(temp_dest_dir, 'docs', 'guide.md'))).to be true + expect(File.exist?(File.join(temp_dest_dir, '_posts', '2023-01-01-test.md'))).to be false + expect(File.exist?(File.join(temp_dest_dir, 'README.md'))).to be false + end + + it 'returns false for non-existent source' do + non_existent_source = File.join(temp_cache_dir, 'non_existent') + result = service.copy_cached_content(non_existent_source, temp_dest_dir) + + expect(result).to be false + end + end + + describe '#cleanup_cache' do + let(:service) { described_class.new(cache_dir: temp_cache_dir) } + + before do + FileUtils.mkdir_p(File.join(temp_cache_dir, 'repo1')) + FileUtils.mkdir_p(File.join(temp_cache_dir, 'repo2')) + File.write(File.join(temp_cache_dir, 'repo1', 'file.txt'), 'content') + end + + it 'cleans entire cache when no repo specified' do + service.cleanup_cache + + expect(Dir.exist?(temp_cache_dir)).to be false + end + + it 'cleans specific repository cache' do + repo_url = 'https://github.com/example/repo1.git' + # Generate the actual hash that would be used + repo_hash = service.send(:generate_repo_hash, repo_url) + + # Create a directory with the actual hash name + actual_repo_dir = File.join(temp_cache_dir, repo_hash) + FileUtils.mkdir_p(actual_repo_dir) + File.write(File.join(actual_repo_dir, 'file.txt'), 'content') + + service.cleanup_cache(repo_url: repo_url) + + expect(Dir.exist?(actual_repo_dir)).to be false + expect(Dir.exist?(File.join(temp_cache_dir, 'repo2'))).to be true + end + end + + describe '#cache_stats' do + let(:service) { described_class.new(cache_dir: temp_cache_dir) } + + it 'returns zero stats for empty cache' do + stats = service.cache_stats + + expect(stats[:total_repos]).to eq(0) + expect(stats[:total_size]).to eq(0) + end + + it 'calculates stats for populated cache' do + repo_dir = File.join(temp_cache_dir, 'test_repo') + FileUtils.mkdir_p(repo_dir) + File.write(File.join(repo_dir, 'test.txt'), 'x' * 1024) # 1KB file + + stats = service.cache_stats + + expect(stats[:total_repos]).to eq(1) + expect(stats[:total_size]).to be > 0 + expect(stats[:cache_dir]).to eq(temp_cache_dir) + end + end + + describe 'private methods' do + let(:service) { described_class.new(cache_dir: temp_cache_dir) } + + describe '#generate_repo_hash' do + it 'generates consistent hash for same URL' do + url = 'https://github.com/example/repo.git' + hash1 = service.send(:generate_repo_hash, url) + hash2 = service.send(:generate_repo_hash, url) + + expect(hash1).to eq(hash2) + expect(hash1).to be_a(String) + expect(hash1.length).to eq(16) + end + + it 'generates different hashes for different URLs' do + url1 = 'https://github.com/example/repo1.git' + url2 = 'https://github.com/example/repo2.git' + hash1 = service.send(:generate_repo_hash, url1) + hash2 = service.send(:generate_repo_hash, url2) + + expect(hash1).not_to eq(hash2) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..632f3fe --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'simplecov' +SimpleCov.start do + add_filter '/spec/' + add_filter '/vendor/' +end + +require 'bundler/setup' +require 'jekyll' +require 'rspec' + +# Require the gem +require 'prexian' + +# Load support files +Dir[File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f } + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + config.filter_run_when_matching :focus + config.example_status_persistence_file_path = 'spec/examples.txt' + config.disable_monkey_patching! + config.warnings = true + + config.default_formatter = 'doc' if config.files_to_run.one? + + config.profile_examples = 10 + config.order = :random + Kernel.srand config.seed + + # Include GitTestHelper for integration tests + config.include GitTestHelper, type: :integration + + # Helper methods + config.include(Module.new do + def fixture_site(name) + File.join(File.dirname(__FILE__), 'fixtures', name) + end + + def build_site(source_dir, config_overrides = {}) + config = Jekyll.configuration( + 'source' => source_dir, + 'destination' => File.join(source_dir, '_site'), + 'quiet' => true, + 'safe' => false + ).merge(config_overrides) + + Jekyll::Site.new(config) + end + + def with_temp_cache_dir + require 'tmpdir' + Dir.mktmpdir('prexian_test_cache') do |cache_dir| + original_cache = ENV['ROP_CACHE_DIR'] + ENV['ROP_CACHE_DIR'] = cache_dir + yield cache_dir + ensure + ENV['ROP_CACHE_DIR'] = original_cache + end + end + end) +end diff --git a/spec/support/git_test_helper.rb b/spec/support/git_test_helper.rb new file mode 100644 index 0000000..6785344 --- /dev/null +++ b/spec/support/git_test_helper.rb @@ -0,0 +1,298 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'fileutils' + +module GitTestHelper + def setup_test_repositories + @test_dir = Dir.mktmpdir('prexian-test') + @hub_repo_path = File.join(@test_dir, 'hub-site') + @project_repo_path = File.join(@test_dir, 'project-site') + + create_hub_repository + create_project_repository + + { + hub_repo_path: @hub_repo_path, + project_repo_path: @project_repo_path, + hub_repo_url: "file://#{@hub_repo_path}", + project_repo_url: "file://#{@project_repo_path}" + } + end + + def cleanup_test_repositories + FileUtils.rm_rf(@test_dir) if @test_dir && Dir.exist?(@test_dir) + end + + private + + def create_hub_repository + FileUtils.mkdir_p(@hub_repo_path) + Dir.chdir(@hub_repo_path) do + # Initialize git repository + system('git init --quiet') + system('git config user.email "test@example.com"') + system('git config user.name "Test User"') + + # Create hub site structure + create_hub_site_files + + # Add and commit files + system('git add .') + system('git commit -m "Initial hub site commit" --quiet') + end + end + + def create_project_repository + FileUtils.mkdir_p(@project_repo_path) + Dir.chdir(@project_repo_path) do + # Initialize git repository + system('git init --quiet') + system('git config user.email "test@example.com"') + system('git config user.name "Test User"') + + # Create project site structure + create_project_site_files + + # Add and commit files + system('git add .') + system('git commit -m "Initial project site commit" --quiet') + end + end + + def create_hub_site_files + # _config.yml for hub site + File.write('_config.yml', <<~YAML) + title: "Test Hub Site" + description: "A test hub site for integration testing" + url: "https://test-hub.example.com" + + # Collections configuration + collections: + projects: + output: true + permalink: /:collection/:name/ + software: + output: true + permalink: /:collection/:name/ + specs: + output: true + permalink: /:collection/:name/ + posts: + output: true + permalink: /blog/:year/:month/:day/:title/ + + # Theme configuration + theme: prexian + + # ROP-specific configuration + prexian: + # Hub site marker + site_type: hub + + # Git repository settings + default_repo_branch: main + refresh_remote_data: last-resort + + # Tag namespaces for categorization + tag_namespaces: + software: + writtenin: "Written in" + audience: "Audience" + specs: + audience: "Audience" + status: "Status" + + # Landing page priority + landing_priority: + - software + - specs + - blog + + # Build settings + markdown: kramdown + highlighter: rouge + + # Plugins + plugins: + - jekyll-feed + YAML + + # Create Gemfile + File.write('Gemfile', <<~RUBY) + source 'https://rubygems.org' + + gem 'jekyll', '~> 4.0' + gem 'prexian', path: '#{Dir.pwd.gsub(@test_dir, '..')}' + gem 'jekyll-feed' + RUBY + + # Create index page + File.write('index.md', <<~MARKDOWN) + --- + layout: home + title: Test Hub Site + --- + + This is a test hub site for integration testing. + MARKDOWN + + # Create a sample project entry + FileUtils.mkdir_p('_projects') + File.write('_projects/test-project.md', <<~MARKDOWN) + --- + title: Test Project + description: A test project for integration testing + site: + git_repo_url: "file://#{@project_repo_path}" + git_repo_branch: main + --- + + This is a test project entry. + MARKDOWN + + # Create sample blog post + FileUtils.mkdir_p('_posts') + File.write('_posts/2024-01-01-test-post.md', <<~MARKDOWN) + --- + title: Test Hub Post + date: 2024-01-01 + author: Test Author + --- + + This is a test blog post from the hub site. + MARKDOWN + end + + def create_project_site_files + # _config.yml for project site + File.write('_config.yml', <<~YAML) + title: "Test Project Site" + description: "A test project site for integration testing" + url: "https://test-project.example.com" + + # Collections configuration + collections: + software: + output: true + permalink: /:collection/:name/ + specs: + output: true + permalink: /:collection/:name/ + posts: + output: true + permalink: /blog/:year/:month/:day/:title/ + + # Theme configuration + theme: prexian + + # ROP-specific configuration + prexian: + # Project site marker (not a hub) + site_type: project + + # Parent hub configuration + hub: + git_repo_url: "file://#{@hub_repo_path}" + git_repo_branch: main + home_url: "https://test-hub.example.com/" + + # Git repository settings + default_repo_branch: main + refresh_remote_data: last-resort + + # Tag namespaces for categorization + tag_namespaces: + software: + writtenin: "Written in" + audience: "Audience" + specs: + audience: "Audience" + status: "Status" + + # Landing page priority + landing_priority: + - software + - specs + - blog + + # Build settings + markdown: kramdown + highlighter: rouge + + # Plugins + plugins: + - jekyll-feed + YAML + + # Create Gemfile + File.write('Gemfile', <<~RUBY) + source 'https://rubygems.org' + + gem 'jekyll', '~> 4.0' + gem 'prexian', path: '#{Dir.pwd.gsub(@test_dir, '..')}' + gem 'jekyll-feed' + RUBY + + # Create index page + File.write('index.md', <<~MARKDOWN) + --- + layout: home + title: Test Project Site + --- + + This is a test project site for integration testing. + MARKDOWN + + # Create sample software entry + FileUtils.mkdir_p('_software') + File.write('_software/test-software.md', <<~MARKDOWN) + --- + title: Test Software + description: A test software package + repo_url: https://github.com/test/software + repo_branch: main + tags: + writtenin: [Ruby] + audience: [Developers] + --- + + This is a test software entry from the project site. + MARKDOWN + + # Create sample specification + FileUtils.mkdir_p('_specs') + File.write('_specs/test-spec.md', <<~MARKDOWN) + --- + title: Test Specification + description: A test specification document + tags: + audience: [Developers] + status: [Draft] + --- + + This is a test specification from the project site. + MARKDOWN + + # Create sample blog post + FileUtils.mkdir_p('_posts') + File.write('_posts/2024-01-02-project-post.md', <<~MARKDOWN) + --- + title: Test Project Post + date: 2024-01-02 + author: Project Author + --- + + This is a test blog post from the project site. + MARKDOWN + + # Create logo file for hub to fetch + FileUtils.mkdir_p('assets') + File.write('assets/logo.svg', <<~SVG) + <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <circle cx="50" cy="50" r="40" fill="blue"/> + <text x="50" y="55" text-anchor="middle" fill="white">LOGO</text> + </svg> + SVG + end +end