Skip to content
/ shp.py Public

Static HTML Preprocessor. Parses custom SHP syntax and uses the data to compile HTML files. In contrary to other preprocessors, this one is meant to be used at build time.

Notifications You must be signed in to change notification settings

Jakub21/shp.py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SHP.py

Static HTML Preprocessor

Compiles HTML from files with easy to read SHP syntax.

You might also want to lake a look at the Javascript port of this project.

Notepad++ SHP syntax coloring

You can enable syntax coloring for SHP in the following way

  • In the top bar, open Language tab
  • Navigate to User Defined Language, then Define your language...
  • Choose Import and select ./npp_udl_shp.xml from this repository
  • Go back to Language tab and select shp from the list.

Installation

To install the package, download it from PIP

pip install shp

CLI usage

There is shp.compile executable module available

Run the following in your terminal

python -m shp.compile <source> <target> <--watch>

Arguments:

  • source (required) - Path to the SHP source file (entry point)
  • target (required) - Path to the target HTML file
  • -w --watch - Recompile whenever entry point file or any other included file is edited

Using SHP as a package

Using SHP class to compile from a single entry-point

from source import SHP

# create an instance
shp = SHP(sourcePath, targetPath)
# compile once
shp.compile()
# compile once, set up a watchdog and block further execution
shp.watch()

Using MultiSHP class to compile from multiple entry-points

from source import MultiSHP

# create an instance
mp = MultiSHP()
# add sources and their corresponding targets
mp.add(firstSource, firstTarget)
mp.add(secondSource, secondTarget)
# compile all sources
mp.compile()
# compile all sources, set up their watchdogs and blocks further execution
mp.watch()

Watching with out blocking

Method responsible for watching can have their blocking disabled by passing noBlock=False argument. This works for both SHP and MultiSHP classes. Make sure the script does not exit.

shp.watch(False)
mp.watch(False)

You should stop the watchers before your program ends.

shp.stop()
mp.stop()

SHP Syntax

General example

// This is a general SHP Syntax example
@doctype
$html {
  $head {
    %meta[charset 'utf-8']
    %link[rel stylesheet href '/stylesheets/index.css']
  }
  $body {
    $div[#Content .bigText] {
      Hello world!
    }
  }
}

Tags and scopes

In SHP there are two tag types - scoped and scopeless.

Scoped tags

Scoped tags are prefixed with $ and are compiled to have both beginning and finishing tokens. For example <div></div>. Scoped tags can have content (text or other tags) inside of them. Add curly brackets to put content inside of a tag $div { Inside }. Omitting brackets of a scoped tag also produces valid code ($div $div creates two tags that are next to each other)

Scopeless tags

Scopeless tags are prefixed with % and are compiled to have only beginning token. For example <img> or <link>. This type of tag can't have any content inside.

Summary

  • $div - Scoped but no content, produces <div></div>
  • $div { Content } - Scoped with content, produces <div> Content </div>
  • %meta - Scopeless, produces <meta>

The tag type is not detected from the tag name. This means you have to choose it yourself.

Parameters

Tags parameters can be added within square brackets []. These brackets must be added right after the tag name.

Generally, parameters can be added to the element by typing its name and then value. In contrary to HTML, equal sign is not used, like so $div[width 300 height 200].

Some parameters can be added using prefixes:

  • To add element ID type it with a # prefix - $div[#FirstElement]
  • To add a class, add . prefix - $div[.Wide .Dark]
  • To set a bool flag to true, add ! prefix - $div[!hidden], $video[!controls]

Prefixed parameters can be mixed freely with name + value ones. The order does not matter.

String enclosing

Parameter values that contain special characters should be enclosed in single ticks '

For example %link[rel stylesheet href 'https://some_cdn.com/file/stylesheet.css']

Relation with content

Parameters should be defined before tag content

$div[#SecondElement .Wide] {
  Hello world
}

Summary

  • %link[rel stylesheet] produces <link rel=stylesheet>
  • $div[width 300] produces <div width=300></div>
  • $div[#ThirdElement width 300] produces <div id=ThirdElement width=300></div>
  • $div[width 300 .Wide .Dark] produces <div width=300 class="Wide Dark"></div>
  • $div[width 300 !hidden] produces <div width=300 hidden=true></div>

Functions

Note: functions are not available in the Javascript port

To call a function, prefix its name with a @. Parameters can be added like it's normal HTML tag. Some functions can also have associated scope, or a body.

@functionName[paramName paramValue] {
  $div { Function body }
}

define[id] { body }

Creates a definition with ID id.

doctype[id]

Adds a doctype clause. id parameter defines the doctype. HTML is the default value of id so in most cases you can omit it completely.

For example @doctype[#OtherDoctype] produces <!DOCTYPE OtherDoctype>

include[file as]

Copies the content of selected file in its own place. Namespaces from included files are preserved.

To wrap the copied content in a namespace, use the optional as parameter. This defines the ID.

Note that file extensions are excluded. The usual ~/ and ./ prefixes probably won't work.

Dy default, all paths are relative to a file that the include statement is present in. To use paths relative to the entry point, add */ prefix. To go to parent directory use ^ (this can be used multiple times).

namespace[id] { body }

Creates a namespace with ID id. Used to avoid name conflicts in define and paste calls. Namespaces can be automatically created by include functions.

Namespaces can be nested. Relative path is always used in other functions' calls.

paste[id from]

Copies body of a definition with ID id to where the function is called. Parameter from selects which namespace to use (by default it's empty, which means current namespace is used). Parameter from is relative to the current location. Use / to access nested namespaces.

Functions - examples

Doctype

File index.shp (entry point)

@doctype
@doctype[#EXAMPLE]

Result

<!DOCTYPE HTML>
<!DOCTYPE EXAMPLE>

General define / paste

File index.shp (entry point)

@define[#foo] {
  $p { Foo content }
}
@paste[#foo]

Result

<p>Foo content</p>

General namespaces

Definitions from namespaces can be accessed in the following ways:

  • Inside the namespace, then its ID is not required
  • Outside the namespace with specifying its ID
  • Inside another namespace with the same ID, not repeating the ID in the paste call

File index.shp (entry point)

@namespace[#bar] {
  @define[#foo] {
    $p { Foo content }
  }
  @paste[#foo]
}

@paste[#foo from bar]

@namespace[$bar] {
  @paste[#foo]
}

Result

<p>Foo content</p>
<p>Foo content</p>
<p>Foo content</p>

Accessing nested namespaces

File index.shp (entry point)

@namespace[#outer] {
  @namespace[#inner] {
    @define[#nested] {
      $p { Nested content }
    }
  }
  @paste[#nested from inner]
}
@paste[#nested from 'outer/inner']

Result

<p>Nested content</p>
<p>Nested content</p>

Simple definition include

File index.shp (entry point)

@include[file bar]
$p { Index was the entry point }
@paste[#foo]

File bar.shp

@define[#foo] {
  $p { But bar.shp content is included }
}

Result

<p>Index was the entry point</p>
<p>But bar.shp content is included</p>

Simple content include

File index.shp (entry point)

@doctype
$html {
  $head {
    %meta[charset 'utf-8']
    @include[file 'brain']
    $title {Example}
  }
  $body
}

File brain.shp

$script[src 'lib/Domi.js']
$script[src 'lib/shp.js']

Result

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset 'utf-8'>
        <script src 'lib/Domi.js'></script>
        <script src 'lib/shp.js'></script>
        <title>Example</title>
    </head>
    <body>
    </body>
</html>

Nested include and directories

File index.shp (entry point)

@doctype
$html {
  $head {
    %meta[charset 'utf-8']
  }
  $body {
    $p {Index content}
    @include[file 'component/footer']
  }
}

File component/footer.shp

$footer {
  $p {This is a footer}
  @include[file copyright]
}

File component/copyright.shp

$p {Made by me, 2022}

Result

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset 'utf-8'>
  </head>
  <body>
    <p>Index content</p>
    <footer>
      <p>This is a footer</p>
      <p>Made by me, 2022</p>
    </footer>
  </body>
</html>

Templates based design

Generally include is used to paste content or definitions into your entry point file.

However, there is an alternative approach. You can define the general tree structure in a template file and add paste statements there. Then, in your entry point file create definitions with content unique for each page. This completely removes the redundancy of the general HTML structure and common head contents.

File index.shp (entry point)

@include [template/general]
@define[#Body] {
  $div { Hello world! }
}

File template/general.shp

@doctype
$html {
  $head {
    %meta[charset 'utf-8']
    $title { Reusability! }
  }
  $body {
    $header { This is a header }
    @paste[#Body]
  }
}

Result

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset 'utf-8'>
    <title> Reusability! </title>
  </head>
  <body>
    <header>This is a header</header>
    <div>Hello world!</div>
  </body>
</html>

About

Static HTML Preprocessor. Parses custom SHP syntax and uses the data to compile HTML files. In contrary to other preprocessors, this one is meant to be used at build time.

Topics

Resources

Stars

Watchers

Forks

Languages