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
, thenDefine your language...
- Choose
Import
and select./npp_udl_shp.xml
from this repository - Go back to
Language
tab and selectshp
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 shp 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 shp 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]
Creates namespace with id as
and pastes all content from file file
in it. Preserves namespaces from included files.
For example @include[file 'bar' as foo]
copies content from ./bar.shp
and saves it in foo
namespace. Note that ./
prefix and file extension is not present.
namespace[id] { body }
Creates a namespace with ID id
. Used to avoid name conflicts in define
and paste
calls. Namespaces are 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 as barNS]
$p { Index was the entry point }
@paste[#foo from barNS]
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 as 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' as footer]
}
}
File component/footer.shp
$footer {
$p {This is a footer}
@include[file copyright as cp]
}
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>