Highlight


Highlight


Usage:

[rexx] highlight [options] file

If file has a .md or a .html extension, process all Rexx fenced code blocks in FILE and highlight them. Otherwise, we assume that this is a Rexx file, and we highlight it directly.

Options:

-a, --ansi Select ANSI SGR terminal highlighting
-h, --html Select HTML highlighting
-l, --latex Select LaTeX highlighting
--noprolog Don't print a prolog
-n, --numberlines   Print line numbers
--patch=patches Apply the semicolon-separated list of patches.
--patchfile=file Apply the patches contained in file.
--prolog Print a prolog (LaTeX driver only)
--startFrom=n Start line numbers at n.
-s, --style=style   Use the rexx-style.css style sheet
--tutor Enable TUTOR-flavored Unicode
-u , --unicode Enable TUTOR-flavored Unicode
-w, --width=n Ensure that lines have width n (ANSI only)

Program source:

#!/usr/bin/env rexx
/******************************************************************************/
/*                                                                            */
/* highlight.rex - Rexx highlighting example                                  */
/* =========================================                                  */
/*                                                                            */
/* This program is part of the Rexx Parser package                            */
/* [See https://rexx.epbcn.com/rexx.parser/]                                  */
/*                                                                            */
/* Copyright (c) 2024-2025 Josep Maria Blasco <josep.maria.blasco@epbcn.com>  */
/*                                                                            */
/* License: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0)  */
/*                                                                            */
/* Date     Version Details                                                   */
/* -------- ------- --------------------------------------------------------- */
/* 20241228    0.1d First public release                                      */
/* 20241229    0.1e Add support for ANSI highlighing                          */
/* 20241231         Add support for LaTeX highlighing                         */
/* 20250102    0.1f Add --prolog and --noprolog options                       */
/* 20250103         Add -u --tutor and --unicode options                      */
/* 20250105         Add --patch="patch specs" and --patchfile=file            */
/* 20250107         Change -t and --term for -a and --ansi                    */
/* 20250108         Add --pad= option                                         */
/*                                                                            */
/******************************************************************************/

Parse Arg fn

-- ::REQUIRES does not work well with "../" paths
package   = .context~package
local     =  package~local
mypath    =  FileSpec( "Path", package~name )
local ~ . = .File~new( mypath"../" )~absolutePath      -- Creates ".."

Call Requires .."/cls/Highlighter.cls"
Call Requires .."/cls/FencedCode.cls"

-- Remember how we were called
Parse Source . how .

-- We will store our processed options in a stem
options. = 0

-- Set default mode to ANSI if called from the command line...
If how == Command Then options.mode = ANSI
-- ...otherwise, assume HTML.
Else                   options.mode = HTML

-- Process user options, if any

patch = .Nil

Loop
  Parse var fn op value rest
If op[1] \== "-" Then Leave
  If op~contains("=") Then Do
    fn = value rest
    Parse Var op op"="value
    Select Case Lower(op)
      When "--startfrom"       Then options.startFrom   = Natural(value)
      When "--style"           Then options.style       = value
      When "--width"           Then options.width       = Natural(value)
      When "--pad"             Then options.pad         = Natural(value)
      When "--patch"           Then Do
        Call AllowQuotes
        If value = "" Then patch = .Nil
        Else patch = .StylePatch~of( value )
      End
      When "--patchfile"       Then Do
        Call AllowQuotes
        If value = "" Then Do
          Say "Invalid option '"op"'."
          Exit 1
        End
        file = Stream(value,"C", "Q Exists")
        If file == "" Then Do
          Say "File '"value"' not found."
          Exit 1
        End
        value = CharIn( file, 1, Chars(file) )~makeArray
        Call Stream file, "C", "Close"
        patch = .StylePatch~of( value )
      End
      Otherwise Do
        Say "Invalid option '"op"'."
        Exit 1
      End
    End
    Iterate
  End
  gotAValue = 1
  Select Case Lower(op)
    When "-s"                  Then options.style       = value
    When "-w"                  Then options.width       = Natural(value)
    Otherwise gotAValue = 0
  End
  If gotAValue Then Do
    fn = rest
    Iterate
  End
  Select Case Lower(op)
    When "-h", "--html"        Then options.mode        =  HTML
    When "-l", "--latex"       Then options.mode        =  LaTeX
    When "-n", "--numberlines" Then options.numberlines = .True
    When "-a", "--ansi"        Then options.mode        =  ANSI
    When "--tutor"             Then options.unicode     = 1
    When "-u", "--unicode"     Then options.unicode     = 1
    When "--noprolog"          Then options.prolog      = 0
    When "--prolog"            Then options.prolog      = 1
    Otherwise Do
      Say "Invalid option '"op"'."
      Exit 1
    End
  End
  fn = value rest
End

-- We need an argument
If fn = "" Then Do
  Say .Resources[Help]
  Exit 1
End

-- Process filenames containing blanks
fn = Strip(fn)
c  = fn[1]
If """'"~contains( c ) Then Do
  Parse Var fn (c)fn2(c)extra
  If extra \== "" Then Do
    Say "Invalid filename '"fn"'."
    Exit 1
  End
  fn = fn2
End

-- Check that the file exists
file = Stream(fn, 'c', 'q exists')
If file == "" Then Do
  Say "File '"fn"' not found."
  Exit 1
End

-- Load the whole file in the "source" array
source = CharIn(file,1,Chars(file))~makeArray
Call Stream file,"c","close"

-- Markdown or html? Process the fenced code blocks and display the result
If file~caselessEndsWith(".md") | file~caselessEndsWith(".html") Then Do
  Say FencedCode( fn, source )
End
-- Assume it's Rexx
Else Do
  hl =  .Highlighter~new(fn, source, options.)
  Do line Over hl~parse( patch )
    Say line
  End
End

Exit

AllowQuotes:
  q = value[1]
  If """'"~contains( q ) Then Do
    fn = value fn
    Parse Var fn (q)value(q)fn
  End
  Return

Natural:
  If DataType(Arg(1),"W"), Arg(1) >= 0 Then Return Arg(1)
  Say "Invalid value" Arg(1)"."
  Exit 1

Requires:
  package~addPackage( .Package~new( Arg(1) ) )
  Return

--------------------------------------------------------------------------------

::Resource Help
Usage: highlight [OPTIONS] FILE
If FILE has a .md or .html extension, process all Rexx fenced code blocks
in FILE and highlight them. Otherwise, we assume that this is a Rexx file,
and we highlight it directly.

Options:
  -a, --ansi             Select ANSI mode
  -h, --html             Select HTML mode
  -l, --latex            Select LaTeX mode
      --noprolog         Do not print a prolog (LaTeX only)
  -n, --numberlines      Print line numbers
      --patch="PATCHES"  Apply semicolon-separated PATCHES
      --patchfile=FILE   Load patches from FILE
      --pad=N            Pad doc-comments and ::resources to N characters
      --prolog           Print a prolog (LaTeX only)
      --startFrom=N      Start line numbers at N
  -s, --style=STYLE      Use "rexx-STYLE.css" (default is "dark")
      --tutor            Enable TUTOR-flavored Unicode
  -u, --unicode          Enable TUTOR-flavored Unicode
  -w, --width=N          Ensure that all lines have width >= N (ANSI only)

The 'highlight' program is part of the Rexx Parser package, and is distributed
under the Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0).

Copyright (c) 2024, 2025 Josep Maria Blasco <josep.maria.blasco@epbcn.com>.
::END