diff --git a/.gitattributes b/.gitattributes index dac38bb..680ddc0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ * text=auto *.css text eol=lf - diff --git a/README.md b/README.md index 7be5897..f860e92 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,97 @@ html mp11.html : mp11.adoc Noto font files are covered under the Open Font License: https://fonts.google.com/noto/use + +## Live Preview for AsciiDoc Documentation + +BoostLook includes a **live preview** feature to streamline local development and review of AsciiDoc documentation. This allows for automatic rendering and real-time browser preview of changes made to [`specimen.adoc`](/doc/specimen.adoc) and [`boostlook.css`](/boostlook.css) files. + +### Overview + +The preview functionality is handled by a Ruby script ([`boostlook_preview.rb`](./boostlook_preview.rb)). This script monitors AsciiDoc, HTML, and CSS files for changes, automatically rebuilding and opening the rendered HTML in your default web browser. + +--- + +### Prerequisites + +Ensure the following dependencies are installed: + +- **Ruby** (>= 2.7 recommended) +- **Asciidoctor** – Install via `gem install asciidoctor` +- **Listen Gem** – Install via `gem install listen` +- **Boost.Build (b2)** – Required for builds invoked by the script. Ensure it is installed and available in your system's PATH. + +--- + +### Usage and Detailed Steps + +1. **Install Ruby and Required Gems**: + - Ensure Ruby is installed on your system. You can check by running: + ```bash + ruby -v + ``` + If Ruby is not installed, follow the instructions on [ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/) to install it. + - Install Asciidoctor: + ```bash + gem install asciidoctor + ``` + - Install the Listen gem: + ```bash + gem install listen + ``` + +2. **Install Boost.Build (b2)**: + - Follow the instructions on the [Boost.Build website](https://boostorg.github.io/build/) to install `b2`. + - Ensure `b2` is available in your system's PATH by running: + ```bash + which b2 + ``` + +3. **Locate Specimen AsciiDoc File**: + - Place your AsciiDoc files in the `doc` directory. The default file is `doc/specimen.adoc`. + - The default file structure should include: + + ``` + /boostlook + ├── doc + │ └── specimen.adoc <-- + │ └── ... + ├── boostlook.css + └── boostlook_preview.rb + └── boostlook.rb + └── ... + ``` + +4. **Navigate to the project directory**: + ```bash + cd /path/to/boostlook + ``` + +5. **Run the preview script**: + - Ensure you're in the root folder + ```bash + ruby boostlook_preview.rb + ``` + +6. **Edit and Preview**: + - Edit your AsciiDoc (specimen.adoc) or CSS (boostlook.css) files. + - The script will automatically detect changes and rebuild the documentation. + - Refresh the browser to see changes. + +7. **Refresh Your Browser** to view changes. + +--- + +### Troubleshooting + +- **Script Not Running**: + - Ensure all prerequisites are installed correctly. + - Check for any error messages in the terminal and resolve them accordingly. + +- **Changes Not Reflecting**: + - Ensure the files being edited are within the monitored directories (`doc` and root directory). + - Verify that the browser is refreshing automatically or manually refresh the browser. + +- **Boost.Build (b2) Not Found**: + - Ensure `b2` is installed and available in your system's PATH. + - Run `which b2` to verify its availability. diff --git a/boostlook_preview.rb b/boostlook_preview.rb new file mode 100755 index 0000000..88b78b2 --- /dev/null +++ b/boostlook_preview.rb @@ -0,0 +1,163 @@ +#!/usr/bin/env ruby + +require 'asciidoctor' +require 'listen' +require 'pathname' +require 'logger' + +# AsciidocRenderer handles building AsciiDoc files, monitoring changes, and rendering the output in a browser. +class AsciidocRenderer + # Define our relevant constant paths + PATHS = { + jamfile: 'doc/Jamfile', + specimen_docinfo_footer: 'doc/specimen-docinfo-footer.html', + specimen_adoc: 'doc/specimen.adoc', + css: 'boostlook.css', + boostlook_rb: 'boostlook.rb', + output_dir: 'doc/html' + }.freeze + + # OS-specific commands to open the default web browser + OS_BROWSER_COMMANDS = { + /darwin/ => 'open', # macOS + /linux/ => 'xdg-open', # Linux + /mingw|mswin/ => 'start' # Windows + }.freeze + + def initialize + # Initialize the logger + @logger = Logger.new($stdout) + @logger.level = Logger::INFO + @logger.formatter = ->(_, _, _, msg) { "#{msg}\n" } + + @file_opened = false # Flag to prevent multiple browser openings + @shutdown_requested = false # Flag to handle graceful shutdown + validate_b2 # Ensure Boost.Build is installed + end + + # Entry point to run the renderer + def run + validate_files # Check for the presence of required files + initial_build # Perform the initial build and render + setup_signal_traps # Setup signal handlers for graceful shutdown + watch_files # Start watching for file changes + end + + private + + # Validates that all required files are present + def validate_files + required_files = [PATHS[:jamfile], PATHS[:specimen_docinfo_footer], PATHS[:specimen_adoc], PATHS[:css], PATHS[:boostlook_rb]] + missing_files = required_files.reject { |file| File.exist?(file) } + unless missing_files.empty? + missing_files.each { |file| @logger.error("Required file #{file} not found") } + exit 1 + end + end + + # Checks if the 'b2' command (Boost.Build) is available + def validate_b2 + unless system('which b2 > /dev/null 2>&1') + @logger.error("'b2' command not found. Please install Boost.Build and ensure it's in your PATH.") + exit 1 + end + end + + # Builds the AsciiDoc project using Boost.Build + def build_asciidoc + Dir.chdir('doc') do + if system('b2') + @logger.info("Build successful") + true + else + @logger.error("Build failed") + false + end + end + end + + # Opens the generated HTML file in the default web browser + def open_in_browser + return if @file_opened + + cmd = OS_BROWSER_COMMANDS.find { |platform, _| RUBY_PLATFORM =~ platform }&.last + if cmd + system("#{cmd} #{PATHS[:output_dir]}/specimen.html") + @file_opened = true + else + @logger.warn("Unsupported OS. Please open #{PATHS[:output_dir]}/specimen.html manually") + end + end + + # Performs the initial build and opens the result in the browser + def initial_build + if build_asciidoc && File.exist?("#{PATHS[:output_dir]}/specimen.html") + open_in_browser + @logger.info("Rendered #{PATHS[:specimen_adoc]} to #{PATHS[:output_dir]}/specimen.html") + else + @logger.error("Failed to generate #{PATHS[:output_dir]}/specimen.html") + end + end + + # Sets up file listeners to watch for changes and trigger rebuilds + def watch_files + @listener = Listen.to('doc', '.') do |modified, added, removed| + handle_file_changes(modified, added, removed) + end + + @listener.ignore(/doc\/html/) # Ignore changes in the output directory + @listener.start + @logger.info("Watching for changes in 'doc' and root directories (excluding 'doc/html')...") + + # Keep the script running until a shutdown is requested + until @shutdown_requested + sleep 1 + end + + shutdown # Perform shutdown procedures + end + + # Handles detected file changes by determining if a rebuild is necessary + def handle_file_changes(modified, added, removed) + @logger.debug("Modified files: #{modified.join(', ')}") + @logger.debug("Added files: #{added.join(', ')}") if added.any? + @logger.debug("Removed files: #{removed.join(', ')}") if removed.any? + + relevant_files = [ + File.expand_path(PATHS[:jamfile]), + File.expand_path(PATHS[:specimen_docinfo_footer]), + File.expand_path(PATHS[:specimen_adoc]), + File.expand_path(PATHS[:css]), + File.expand_path(PATHS[:boostlook_rb]) + ] + + # Check if any of the changed files are relevant for rebuilding + changes_relevant = (modified + added + removed).any? do |file| + relevant_files.include?(File.expand_path(file)) + end + + if changes_relevant + @logger.info("Relevant changes detected, rebuilding...") + if build_asciidoc && File.exist?("#{PATHS[:output_dir]}/specimen.html") + open_in_browser + @logger.info("Re-rendered successfully") + end + end + end + + # Sets up signal traps to handle graceful shutdown on interrupt or terminate signals + def setup_signal_traps + Signal.trap("INT") { @shutdown_requested = true } + Signal.trap("TERM") { @shutdown_requested = true } + end + + # Performs shutdown procedures, such as stopping the file listener + def shutdown + @logger.info("Shutting down...") + @listener.stop if @listener + exit + end +end + +# Instantiate and run the AsciidocRenderer +AsciidocRenderer.new.run diff --git a/doc/specimen.adoc b/doc/specimen.adoc index 0d0e5cc..670cce6 100644 --- a/doc/specimen.adoc +++ b/doc/specimen.adoc @@ -438,7 +438,7 @@ See <> for description. [#integral_usage_notes_] * All built-in integral types are allowed except bool which is deleted * from_chars for integral type is constexpr (BOOST_CHARCONV_CONSTEXPR is defined) when: -** compiled using `-std=c++14` or newer +** compiled using `-std=c++14` or newer ** using a compiler with `\__builtin_ is_constant_evaluated` * These functions have been tested to support `\__int128` and `unsigned __int128` @@ -993,4 +993,3 @@ This documentation is copyright 2022-2023 Peter Dimov and Matt Borland and is di the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0]. :leveloffset: -1 -