code:
Most writing is basically just plain text with branches for different cases.
Important cases to remember:
-Does the slave have a dick/vagina/tits?
-Does the PC have a dick/tits/vagina?
-If the slave has a dick and you plan to use it, can they get erect on their own?
-Is the PC pregnant?
-Is the slave mindbroken?
-Is the slave blind?
-Is the slave pregnant?
-If you penetrate an orifice, is the slave a virgin in that orifice?
-Can the slave move (i.e. is the slave amputated and/or have massive tits and/or a massive dick)?
-Does the slave like/hate trust/fear you?
-Does the slave have fetishes or personality/sexual quirks/flaws that would impact how they react?

It's important to handle every RELEVANT variation of the above cases. Depending on what you're writing, other cases may also warrant consideration; e.g. a breast-play scene will probably need to account for breast size and lactation a lot more than a generic fuck scene. If a scene only applies to a specific type of slave, you can restrict the cases you account for to the ones that are only possible for that type of slave.

There are, broadly speaking, two main ways to do this:
1. Write the same big block of text and then copy/paste it into each relevant case, and tweak the wording slightly.
 ++Less thinking required
 --LOTS of duplicate text; if you need to change something later, you have to change it in many places
2. Decompose the scene into smaller segments or sections that can each be swapped out for the slave type in question.
 ++MUCH easier maintenance
 --Have to figure out how to phrase the scene in a way that can be broken down into discrete chunks

You can also combine the two cases. For example, you may want a complex, interleaved set of <<if>> conditions for the common cases, and then have big blobs for the corner cases that are hard to handle.

It can also be easier if you split the scene into a "prep", "main", and "finish" section (these would all be the same writing block, just 2-3 <<if>> chains in sequence); that way, you could, for example, take all the various possible cases that aren't really unique, and narrate them so that, in the main section, you don't have to do so much explanatory writing for the scene to make sense overall. Then, if necessary, wrap up with the finish section. (See src/pregmod/fSlaveSelfImpreg.tw for an example of this approach)

All the variables for you and the slave are, generally, held in the variables $PC and $activeSlave, respectively. When the PC and the slave have the same attributes, they usually have the same name, but exactly what they do and mean can vary a little bit. RTFM for details. To access a specific variable, you do $var.attribName, so e.g. pregnancy is checked by $activeSlave.preg or $PC.preg. In rarer cases, you'll be dealing with an indexed entry in the $slaves array; if that's the case, you'd use $slaves[_u] or $slaves[$i] (or whatever) instead of $activeSlave. If a second (or third?) slave is present, it will be stored in another variable, the name of which will depend on the scene; for example, when impregnating a slave with another slave, the sperm donor is in $impregnatrix

Conditions are usually checked by
<<if CONDITION>>
SHOW THIS TEXT
<<elseif CONDITION2>>
SHOW THIS TEXT INSTEAD
<<else>>
IF NEITHER CONDITION IS MET, SHOW THIS
<</if>>

Conditions are usually comparative (i.e. $a < $b or $b == 5 or $c != $d.e) and can be chained together with && (and) or || (or) and grouped with parens () - for example:
<<if($a > 1 || ($b == 2 && $c != $a))>>
lets you check that either variable A is greater than one, or both B equals two and C is not equal to A.
There are also a few misc functions that let you check things like whether a slave can get pregnant or whether two slaves are mutually fertile

Variable names are interpolated straight into the text, so if you have a slave named "Beth" then the text
"You fuck $activeSlave.slaveName" becomes "You fuck Beth"
You can also explicitly print a variable by doing <<print _varName>> (for temp variables) or <<print $varName>> (for global variables) which can be useful in various situations; it also lets you print the output of code expressions, like <<print $someNumericVar * 10>>.
If you want to change a variable, you do something like
<<set $activeSlave.devotion += 5>>
Which would, in this case, give a devotion increase of 5.

Colour is changed with YOUR @@.colorname;COLORED TEXT@@ HERE

So a random (really stupid) example might be:
<<if $activeSlave.fetish == 'mindbroken'>>
	Special big block of mindbroken text here.
<<elseif $activeSlave.amp == 1>>
	Special big block of amputee text here.
<<if $PC.dick > 0>>
	<<set _wasVirgin = ''>>
	<<if $activeSlave.vagina > -1>>
		<<set _orifice = 'vagina'>>
		<<if $activeSlave.vagina == 0>>
			<<set _wasVirgin = 'vaginal'>>
		<</if>>
	<<else>>
		<<set _orifice = 'ass'>>
		<<if $activeSlave.anus == 0>>
			<<set _wasVirgin = "anal">>
		<</if>>
	<</if>>
	You fuck $activeSlave.slaveName's _orifice.
	<<if _wasVirgin != ''>>
		<<if $activeSlave.devotion > 50>>
			She @@.hotpink;loves@@ losing her <<print _wasVirgin>> virginity to you.
			<<set $activeSlave.devotion += 5>>
		<<else>>
			She @@.mediumorchid;@@dislikes losing her <<print _wasVirgin>> virginity to you.
			<<set $activeSlave.devotion -= 5>>
		<</if>>
	<</if>>
<<elseif $PC.vagina > 0 && $activeSlave.dick > 0>>
	$activeSlave.slaveName
	<<if $activeSlave.devotion > 50>>
		@@.hotpink;lovingly penetrates@@
		<<set $activeSlave.devotion += 5>>
	<<else>>
		indifferently hammers
	<</if>>
	your pussy.
<<elseif $activeSlave.vagina > -1>>
	Lesbian sex here.
<<else>>
	ERROR - PC doesn't have dick or vagina?
<</if>>

If you need to set a temp variable, prefix it with _ instead of $. This way, you don't go cluttering up the world with variables that only matter inside your little writing segment.

You can "hot-test" stuff by text editing your HTML copy of FC, but it's a little more tedious because you have to "escape" double quotes, <, >, and & with " , < > and & respectively.

Some hints:
1. Write your logic first, so you don't forget to close tags. In other words, it's better to do
<<if $cond>
TODO
<<else>>
TODO
<</if>>
And then fill it in later than it is to end up with a situation where you have a dozen unclosed tags and you can't remember where they are or what they do.

2. For very simple stuff, it's fine to "inline" your stuff. For example, when penetrating a slave, doing "you fuck her <<if $activeSlave.vagina > -1>>pussy<<else>>ass<</if>>" to show "pussy" or "ass" as necessary. However, if you need to do the same comparison a bunch of times, do something like
<<if $activeSlave.vagina > -1>>
	<<set _targetOrifice = "vagina">>
<<else>>
	<<set _targetOrifice = "asshole">>
<</if>
And then, when you need it, do "you fuck her _targetOrifice" in sixteen different places without having the pain in the ass of copy/pasting the same if/else clause every time.

3. INDENT YOUR LOGIC. USE TABS. I'm serious. Don't question me. It will make EVERYONE hate you, when they have to deal with your code, if it's not indented properly.
This is much easier to read:
<<if $cond1>>
	<<if $cond2>>
		whatever
	<<elseif $cond3>>
		whatever
	<<elseif $cond4>>
		<<if $cond5>>
			whatever
		<<else>>
			<<if $cond6>>
				whatever
			<</if>>
		<</if>>
	<<else>>
		whatever
		<<if $cond7>>
			<<if $cond8>>
				whatever
			</if>
		</if>>
	<</if>>
<</if>>
than this:
<<if $cond1>>
<<if $cond2>>
whatever
<<elseif $cond3>>
whatever
<<elseif $cond4>>
<<if $cond5>>
whatever
<<else>>
<<if $cond6>>
whatever
<</if>>
<</if>>
<<else>>
whatever
<<if $cond7>>
<<if $cond8>>
whatever
</if>
</if>>
<</if>>
<</if>>

4. Proof-read your shit before posting it. Spell-check wouldn't hurt.


code:
There are two main changes here.

CHANGE 1: Speed up loading
==========================

The first is to speed up loading the file.

Edit the sugarcube header and change:

	{key:"_serialize",value:function(e)
	...
	 return Object.freeze

Into this

[{key:"_serialize",value:function(e)
/* look here for changes */
{return JSON.stringify(e)}},{key:"_deserialize",value:function(e){return JSON.parse((!e || e[0]=="{")?e:LZString.decompressFromUTF16(e))}}]),e}();
/* changes end here */
return Object.freeze

CHANGE 2: Remember scroll position
==================================

Edit the sugarcube header and change:

    var t=jQuery(this);t.is("[aria-pressed]")&&t.attr("aria-pressed","true"===t.attr("aria-pressed")?"false":"true"),e.apply(this,arguments);

into this:

    var t=jQuery(this);
    const dataPassage = t.attr('data-passage');
    const initialDataPassage = window && window.SugarCube && window.SugarCube.State && window.SugarCube.State.passage;
    const savedYOffset = window.pageYOffset;
    t.is("[aria-pressed]")&&t.attr("aria-pressed","true"===t.attr("aria-pressed")?"false":"true"),e.apply(this,arguments);
    const doJump = function(){ window.scrollTo(0, savedYOffset); }
    if ( dataPassage && (window.lastDataPassageLink === dataPassage || initialDataPassage === dataPassage))
        doJump();
    window.lastDataPassageLink = dataPassage;

This uses two separate methods to detect if we are returning to the same page.  Just checking sugarcube should actually be sufficient, but
no harm in this approach either.

path: devNotes/sugarcube stuff/important for updating sugarcube.txt


code:
/*:: Main stylesheet [stylesheet]*/

body {
	overflow-x: hidden;
}

/* clears SugarCube's default transition */
.passage {
    transition: none;
    -webkit-transition: none;
}

.passage-in {
    opacity: 1 !important;
}

/* default is 54em */
#passages {
    max-width: 100%;
}

/* small trick to hide broken images */
img {
    text-indent: -10000px;
}

.imageRef {
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    align-items: flex-start;
    position: relative;
	background-color: rgba(80, 80, 80, 0.5);
	margin: 2px;
}

.tinyImg {
    height: 120px;
    width: 120px;
    float: left;
}

.smlImg {
    height: 150px;
    width: 150px;
    float: left;
}

.smlImg > img, .smlImg > video {
	height: auto;
}

.medImg {
    height: 300px;
    width: 300px;
    float: right;
}

.medImg > img, .medImg > video {
	height: auto;
}

.lrgRender {
    height: 531px;
    width: 531px;
	margin-right: -50px;
	margin-left: -50px;
    float: right;
	z-index: -1;
}

.lrgVector {
    height: 600px;
    width: 600px;
	margin-right: -125px;
	margin-left: -125px;
    float: right;
	z-index: -1;
}

.lrgRender > div.mask {
	width: 150px;
	height: 100%;
	background: linear-gradient(90deg, rgba(17,17,17,1), rgba(17,17,17,0.8) 60%, rgba(17,17,17,0));
	z-index: 1;
}

.lrgRender > img, .lrgRender > video {
	margin-left: -150px;
	height: 531px;
	width: auto;
}

.lrgVector > div.mask {
	width: 150px;
	height: 100%;
	background: linear-gradient(90deg, rgba(17,17,17,1), rgba(17,17,17,0.8) 60%, rgba(17,17,17,0));
	z-index: 1;
}

.lrgVector > img, .lrgVector > video {
	margin-left: -150px;
	height: 600px;
	width: auto;
}

.lrgVector svg {
    width: 336px;
    height: 600px;
}

object {
    object-fit: scale-down;
    position: absolute;
    top: 0;
	left: 0;
}

img.paperdoll {
    position: absolute;
    top: 0;
	left: 0;
	margin-left: 0;
}

span.plusButton {
	display: inline-block;
	line-height: 25px;
	width: 20px;
	text-align: center;
	border: 1px solid rgba(0, 139, 0, 0.3);
	background: rgba(0, 139, 0, 0.2);
	margin: 2px 0;
}

span.minusButton {
	display: inline-block;
	line-height: 25px;
	width: 20px;
	text-align: center;
	border: 1px solid rgba(184, 0, 0, 0.3);
	background: rgba(184, 0, 0, 0.2);
	margin: 2px 0;
}

span.zeroButton {
	display: inline-block;
	line-height: 25px;
	width: 20px;
	text-align: center;
	border: 1px solid rgba(0, 0, 255, 0.3);
	background: rgba(0, 0, 255, 0.2);
	margin: 2px 0;
}

span.plusButton:hover { background: rgba(0, 139, 0, 0.4); }
span.minusButton:hover { background: rgba(184, 0, 0, 0.4); }
span.zeroButton:hover { background: rgba(0, 0, 255, 0.4); }

span.plusButton > a { display: block; }
span.minusButton > a { display: block; }
span.zeroButton > a { display: block; }

span.plusButton > a:hover { text-decoration: none; }
span.minusButton > a:hover { text-decoration: none; }
span.zeroButton > a:hover { text-decoration: none; }

/* Colors are made as css classes, to allow them to be
   changed for a light color scheme (for example). */
.link { color: #68D } /* link color */
.aquamarine { color: aquamarine }
.coral { color: coral }
.cyan { color: cyan }
.darkgoldenrod { color: darkgoldenrod }
.darkred { color: darkred }
.darkviolet { color: darkviolet }
.deeppink { color: deeppink }
.deepskyblue { color: deepskyblue }
.gold { color: gold }
.goldenrod { color: goldenrod }
.gray { color: gray }
.green { color: green }
.hotpink { color: hotpink }
.lawngreen { color: lawngreen }
.lightblue { color: lightblue }
.lightcoral { color: lightcoral }
.lightgreen { color: lightgreen }
.lightpink { color: lightpink }
.lightsalmon { color: lightsalmon }
.lime { color: lime }
.limegreen { color: limegreen }
.magenta { color: magenta }
.mediumaquamarine { color: mediumaquamarine }
.mediumorchid { color: mediumorchid }
.mediumseagreen { color: mediumseagreen }
.orange { color: orange }
.orangered { color: orangered }
.orchid { color: orchid }
.pink { color: pink }
.red { color: red }
.seagreen { color: seagreen }
.springgreen { color: springgreen }
.tan { color: tan }
.teal { color: teal }
.yellow { color: yellow }
.yellowgreen { color: yellowgreen }

.link a { color: #68D } /* link color */
.aquamarine a { color: aquamarine }
.coral a { color: coral }
.cyan a { color: cyan }
.darkgoldenrod a { color: darkgoldenrod }
.darkred a { color: darkred }
.darkviolet a { color: darkviolet }
.deeppink a { color: deeppink }
.deepskyblue a { color: deepskyblue }
.gold a { color: gold }
.goldenrod a { color: goldenrod }
.gray a { color: gray }
.green a { color: green }
.hotpink a { color: hotpink }
.lawngreen a { color: lawngreen }
.lightblue a { color: lightblue }
.lightcoral a { color: lightcoral }
.lightgreen a { color: lightgreen }
.lightpink a { color: lightpink }
.lightsalmon a { color: lightsalmon }
.lime a { color: lime }
.limegreen a { color: limegreen }
.magenta a { color: magenta }
.mediumaquamarine a { color: mediumaquamarine }
.mediumorchid a { color: mediumorchid }
.mediumseagreen a { color: mediumseagreen }
.orange a { color: orange }
.orangered a { color: orangered }
.orchid a { color: orchid }
.pink a { color: pink }
.red a { color: red }
.seagreen a { color: seagreen }
.springgreen a { color: springgreen }
.tan a { color: tan }
.teal a { color: teal }
.yellow a { color: yellow }
.yellowgreen a { color: yellowgreen }

/*! <<checkvars>> macro for SugarCube 2.x */
#ui-dialog-body.checkvars{padding:1em}#ui-dialog-body.checkvars h1{font-size:1em;margin-top:0}#ui-dialog-body.checkvars table{border-collapse:collapse;border-spacing:0}#ui-dialog-body.checkvars thead tr{border-bottom:2px solid #444}#ui-dialog-body.checkvars tr:not(:first-child){border-top:1px solid #444}#ui-dialog-body.checkvars td,#ui-dialog-body.checkvars th{padding:.25em 1em}#ui-dialog-body.checkvars td:first-child,#ui-dialog-body.checkvars th:first-child{padding-left:.5em;border-right:1px solid #444}#ui-dialog-body.checkvars td:last-child,#ui-dialog-body.checkvars th:last-child{padding-right:.5em}#ui-dialog-body.checkvars th:first-child{text-align:center}#ui-dialog-body.checkvars td:first-child{font-weight:700;text-align:right}#ui-dialog-body.checkvars td{font-family:monospace,monospace;vertical-align:top;white-space:pre-wrap}#ui-dialog-body.checkvars .scroll-pad{margin:0;padding:0}

/*! <<bugreport>> macro for SugarCube 2.x */
#ui-dialog-body.bugreport #bugreport-info{margin-bottom:1em}#ui-dialog-body.bugreport #bugreport-data{display:block;overflow:auto;font-family:monospace,monospace;background-color:transparent;border:1px solid #444;margin:0;padding:6px;height:auto;min-height:200px;white-space:normal}#ui-dialog-body.bugreport .scroll-pad{margin:0;padding:0}

div.output{
width: 100%;
width: 100vw;
max-width: 100%;
word-break: break-all;
white-space: normal;
}

/*:: accordionStyleSheet [stylesheet]*/
/* Accordion 000-250-006 */

button.accordion {
    cursor: pointer;
    padding: 5px;
    width: 100%;
    margin-bottom: 10px;
    border-bottom: 3px double;
    border-right: 3px double;
    border-left: none;
    border-top: none;
    text-align: left;
    outline: none;
    transition: 0.2s;
    background-color: transparent;
}

button.accordion.active, button.accordion:hover {
    background-color: transparent;
}

button.accordion:before {
    content: '\002B';
    color: #777;
    font-weight: bold;
    float: left;
    margin-right: 5px;
}

.unStaffed {
    background-color: transparent;
    padding: .5em .2em;
    margin-bottom: 1em;
    border-bottom: thin inset grey;
}

.unStaffed:before {
    content: '\00D7';
    color: #777;
    font-weight: bold;
    color: red;
    float: left;
    margin-right: 5px;
}

.unStaffed:after {
    content: attr(data-after);
    float: right;
    margin-left: 2em;
    font-weight: strong;
    color: green;
}

button.accordion:after {
    content: attr(data-after);
    float: right;
    margin-left: 2em;
    font-weight: normal;
    color: green;
}

button.accordion.active:before {
    content: "\2212";
}

.accHidden {
    padding: 0 18px;
	margin-top: 5px;
	margin-bottom: 5px;
    max-height: 0;
    overflow: hidden;
}

/* begin efmCSS */

#graph .linage {
    fill: none;
    stroke: white;
}
#graph .marriage {
    fill: none;
    stroke: white;
}
#graph .node {
    background-color: lightblue;
    border-style: solid;
    border-width: 1px;
}
#graph .nodeText{
    font: 10px sans-serif;
    margin: 0;
    padding: 0;
    color: black;
}
#graph {
    font: 10px sans-serif;
    margin: -20px 0;
    padding: 0;
    color: black;
}

#graph div {
    border-style: solid;
    border-width: 1px;
}

#graph .XY {
    background-color: lightblue;
}

#graph div.XX {
    background-color: pink;
}

#graph div.unknown {
    background-color: gray;
}

#graph div.unknownXY {
    background-color: #808080;
}

#graph div.unknownXX {
    background-color: #808080;
}

#graph .you {
    background-color: red;
}

#graph .emphasis {
    font-style: italic;
    font-weight: bold;
    margin: 0;
    background: #ffffff88;
    border: 2px solid Gold;
	white-space: nowrap;
}

#editFamily {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

#editFamily #familyTable {
}

/* end extended family css */

config.history.tracking = false;

/*:: Quick List stylesheet [stylesheet]*/

.hidden
{
    display:none;
}

div.quicklist.devotion button.mindbroken
{
    background-color:red;
}
div.quicklist.devotion button.very-hateful
{
    background-color:darkviolet;
}
div.quicklist.devotion button.hateful
{
    background-color:darkviolet;
}
div.quicklist.devotion button.resistant
{
    background-color:mediumorchid;
}
div.quicklist.devotion button.ambivalent
{
    background-color: yellow;
    color: #444444;
}
div.quicklist.devotion button.accepting
{
    background-color: hotpink;
}
div.quicklist.devotion button.devoted
{
    background-color: deeppink;
}
div.quicklist.devotion button.worshipful
{
    background-color: magenta;
}
div.quicklist.trust button.mindbroken
{
    background-color:red;
}
div.quicklist.trust button.extremely-terrified
{
    background-color: darkgoldenrod;
}
div.quicklist.trust button.terrified
{
    background-color: goldenrod;
}
div.quicklist.trust button.frightened
{
    background-color: gold;
    color: coral;
}
div.quicklist.trust button.fearful
{
    background-color: yellow;
    color: green;
}
div.quicklist.trust button.hate-careful
{
    background-color: orange;
}
div.quicklist.trust button.careful
{
    background-color: mediumaquamarine;
    color: forestgreen;
}
div.quicklist.trust button.bold
{
    background-color: orangered;
}
div.quicklist.trust button.trusting
{
    background-color: mediumseagreen;
}
div.quicklist.trust button.defiant
{
    background-color: darkred;
}
div.quicklist.trust button.profoundly-trusting
{
    background-color: seagreen;
}
div.quicklist
{
    table-layout: fixed;
    text-align: center;
    border-collapse: separate;
    border-spacing: 2px;
    border-style: hidden;
    empty-cells: hide;
    width: 70%;
}
div.quicklist button
{
    margin-top: 15px;
    margin-right: 20px;
    white-space: nowrap;
}

path: devNotes/twine CSS


code:
#!/bin/sh Requires MEGAcmd, git, mv, cd, mkidr, echo
COUNTER=0 LocalDir=~/fc-pregmod/ RemoteDir=FC-GIT U=anon@anon.anon P=13245 && mega-login $U $P
while [ $COUNTER -ge 0 ]; do
	if [ $COUNTER -eq 0 ]; then mega-login $U $P && mega-mkdir $RemoteDir && mega-export -a $RemoteDir && mkdir $LocalDir ; git clone -q https://gitgud.io/pregmodfan/fc-pregmod.git $LocalDir
	fi
	cd $LocalDir/ && AbrevHash=`git log --reverse -n1 --abbrev-commit |grep -m1 commit | sed 's/commit //'`
	if [ $COUNTER -eq 0 ]; then ./compile > /dev/null && mv bin/FC_pregmod.html "bin/FC-pregmod-$(git log -1 --format='%cd' --date='format:%F-%H-%M')-$AbrevHash.html" && mega-put bin/*.html $RemoteDir
	else if [ "$(git pull)" == "Already up to date." ]; then ehco -n ""
		else clear && rm bin/*.html ; ./compile > /dev/null && mv bin/FC_pregmod.html "bin/FC-pregmod-$(git log -1 --format='%cd' --date='format:%F-%H-%M')-$AbrevHash.html" && mega-login $U $P && mega-put bin/*.html $RemoteDir
		fi
	fi
	let COUNTER=COUNTER+1 && mega-logout > /dev/null && sleep 300s
done


code:
#!/bin/sh Requires MEGAcmd, git, mv, cd, mkidr, echo
COUNTER=0 LocalDir=~/fc-pregmod/ RemoteDir=FC-GIT U=anon@anon.anon P=13245 && mega-login $U $P
while [ $COUNTER -ge 0 ]; do
	if [ $COUNTER -eq 0 ]; then mega-login $U $P && mega-mkdir $RemoteDir ; mega-export -a $RemoteDir ; mkdir $LocalDir ; git clone -q https://gitgud.io/pregmodfan/fc-pregmod.git $LocalDir
	fi
	cd $LocalDir/ && AbrevHash=`git log -n1 --reverse --abbrev-commit |grep -m1 commit | sed 's/commit //'`
	if [ $COUNTER -eq 0 ]; then echo "First run. Compliling, formatting and placing .html." && ./compile > /dev/null && mv bin/FC_pregmod.html "bin/FC-pregmod-$(git log -1 --format='%cd' --date='format:%F-%H-%M')-$AbrevHash.html" && mega-put bin/*.html $RemoteDir && echo "Inital compiled .html placed."
	else if [ "$(git pull)" == "Already up to date." ]; then echo "No updated files."
		else echo "Compliling, formatting and placing updated .html." ; rm bin/*.html ; ./compile > /dev/null && mv bin/FC_pregmod.html "bin/FC-pregmod-$(git log -1 --format='%cd' --date='format:%F-%H-%M')-$AbrevHash.html" && mega-login $U $P && mega-put bin/*.html $RemoteDir ; echo "Updated .html placed."
		fi
	fi
	let COUNTER=COUNTER+1 && mega-logout > /dev/null && sleep 300s
done


code:
#!/bin/sh

# setup:
# add this to crontab:
# */15 * * * * cd ~/FC/fc-pregmod && git pull --ff-only origin pregmod-master > ~/FC/git_pull.log 2>&1
# and do "ln -s ~/FC/fc-pregmod/devTools/BuildAndIPFSify.sh .git/hooks/post-merge"
# NOTE: you may need to define XDG_RUNTIME_DIR in your crontab

# TODO: add logic to figure out if we should use ipfs-cluster instead of using the local instance directly.
#			if we use ipfs-cluster we probably don't need to warm ipfs.io's cache.

rm bin/*.html

# if this script is used as the post-merge hook then
# a) git pull has pulled something
# b) $PWD is fc-pregmod
if [ "$(basename "$0")" != "post-merge" ]; then
	# cd to fc-pregmod based on where this script is
	cd "$(readlink -f "$(dirname "$0")")/.." || exit 1
	git pull
	# if there are new html files then git probably pulled something and ran this script as a post-merge hook
	if ls bin/*.html; then
		exit 0
	fi
fi

# If we've done this before then unpin the previous hash so IPFS can GC it if it needs to
if [ -r ../IPFS_hash.txt ]; then
	ipfs pin rm --recursive=true "$(cut -d : -f 2 ../IPFS_hash.txt | tr -d ' ')"
fi

./compile || exit 1

# Keep the build time from changing the hash of the file
sed -Ei -e '/^  \* Built on .+$/d' bin/*.html

# add the date of the last commit to the file, but don't use colons because Windows (still?) doesn't like them
mv bin/*.html "bin/FC pregmod $(git log -1 --format='%cd' --date='format:%F %H-%M').html"

# include the unembedded vector art
ipfs_hash="$(ipfs add -w -Q -r bin/*.html resources)"
echo "IPFS Folder Hash: ${ipfs_hash}" > ../IPFS_hash.txt
ipfs name publish "$ipfs_hash"

# when it's done it will print something like "Published to $your_pubkey: /ipfs/$ipfs_hash"
# You can view the folder at http://127.0.0.1:8080/ipns/$your_pubkey

# make ipfs.io cache the files
# $XDG_RUNTIME_DIR SHOULD be defined, but there are cases where it wouldn't be
if [ -z "${XDG_RUNTIME_DIR+x}" ]; then
	echo "\$XDG_RUNTIME_DIR is unset, bailing"
	exit 2
fi

# throw it into a file so we can loop over lines, not "strings delimited by whitespace"
find resources bin -print | grep -ve '.gitignore' | sed -e 's|bin/||' | grep -Ee '.+\.svg' -e '.html' > "${XDG_RUNTIME_DIR}/files.list"

# ipfs PeerID, it's user specific
PeerID="$(ipfs config show | grep -e 'PeerID' | cut -d: -f 2 | tr -d ' "')"
while IFS= read -r item
do
	echo "https://ipfs.io/ipns/${PeerID}/${item}"
done < "${XDG_RUNTIME_DIR}/files.list" | xargs --max-procs=10 --max-args=1 --replace curl --silent --show-error --range 0-499 --output /dev/null '{}'

rm "${XDG_RUNTIME_DIR}/files.list"


code:
#!/usr/bin/env python3
import fileinput
import re
import sys

WARNING = '\033[93m'
ENDC = '\033[0m'

def myprint(*args):
    print(WARNING, fileinput.filename() + ":", ENDC,*args)

def yield_line_and_islastline(f):
    global filename
    global linenumber
    try:
        prevline = next(f)
        filename = fileinput.filename()
        linenumber = fileinput.filelineno()
    except StopIteration:
        return
    for line in f:
        yield (prevline, f.isfirstline())
        filename = fileinput.filename()
        linenumber = fileinput.filelineno()
        prevline = line
    yield prevline, True

pattern = re.compile(r'(<<(\/?) *(if|for|else|switch|case|replace|link)[^<>]*)')

tagfound = []
try:
    for line, isLastLine in yield_line_and_islastline(fileinput.input()):
        for (whole,end,tag) in re.findall(pattern,line):
            if tag == "else" or tag == 'case':
                if len(tagfound) == 0:
                    myprint("Found", tag, "but with no opening tag:")
                    myprint("  ", linenumber,":", whole)
                    fileinput.nextfile()
                lasttag = tagfound[-1]
                if (tag == "else" and lasttag["tag"] != "if") or (tag == "case" and lasttag["tag"] != "switch"):
                    myprint("Mismatched else: Opening tag was:")
                    myprint("  ",lasttag["linenumber"],":", lasttag["whole"])
                    myprint("But this tag was:")
                    myprint("  ",linenumber,":", whole)
                    fileinput.nextfile()
                    break
            elif end != '/':
                tagfound.append({"whole": whole, "linenumber":linenumber,"tag":tag})
            else:
                if len(tagfound) == 0:
                    myprint("Found closing tag but with no opening tag:")
                    myprint("  ", linenumber,":", whole)
                    fileinput.nextfile()
                    break
                lasttag = tagfound.pop()
                if lasttag["tag"] != tag:
                    myprint("Mismatched tag: Opening tag was:")
                    myprint("  ",lasttag["linenumber"],":", lasttag["whole"])
                    myprint("Closing tag was:")
                    myprint("  ",linenumber,":", whole)
                    fileinput.nextfile()
                    break

        if isLastLine:
            if len(tagfound) != 0:
                myprint("End of file found but", len(tagfound), ("tag hasn't" if len(tagfound)==1 else "tags haven't"), "been closed:")
            for tag in tagfound:
                myprint("  ", tag["linenumber"],":", tag["whole"])
            tagfound = []
except UnicodeDecodeError as e:
    myprint(e)
    print("   Hint: In linux, you can get more details about unicode errors by running:")
    print("     isutf8", fileinput.filename())
    print("   :Note it might be caused by ", filename)


code:
#!/usr/bin/env python3

'''
Script for embedding favicons into the SugarCube Header.

Script file is expected to reside in devTools directory.

Note: This does not actually check the image file's contents for size detection.

Usage:
python3 embed_favicon.py
'''

import sys
import os
import re
import base64

# file extensions eligible for use as favicons and their mimetype
ext2mimetype = {
    '.png': 'image/png',
    '.ico': 'image/x-icon'
}

# reads a file, turns it into a data uri
def data_uri_from_file(filename, mimetype):
    data = open(filename,'rb').read()
    base64data = base64.b64encode(data).decode('ascii')
    out = 'data:%s;base64,%s'%(mimetype, base64data)
    return out

if __name__ == "__main__":
    # find project root directory path
    # (script file is expected to reside in devTools)
    project_root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    # path to SugarCube's header.html
    header_html_path = os.path.join(
        project_root_path,
        'devTools/tweeGo/storyFormats/sugarcube-2/header.html'
    )
    # path to directory containing all favicons to embed
    favicons_source_path = os.path.join(
        project_root_path,
        'resources/raster/favicon/'
    )
    # walk directory for all files
    favicons_paths = [
    os.path.join(dirpath, filename)
    for dirpath, dirnames, filenames in os.walk(favicons_source_path)
    for filename in filenames
    ]
    # ignore files with unknown extensions
    favicons_paths = [f for f in favicons_paths if f[-4:] in ext2mimetype.keys()]

    # prepare embedded data
    size_from_filename = re.compile(r'([0-9]+)\....$')
    favicons_html = []
    for fp in favicons_paths:
        # get mimetype by file extension
        mimetype = ext2mimetype[fp[-4:]]
        if (mimetype == 'image/x-icon'):
            # assume sizes in ico
            sizes = '16x16 32x32 64x64'
        else:
            # guess icon size from file name
            size = size_from_filename.search(fp).group(1)
            sizes = '%sx%s'%(size, size)
        data = data_uri_from_file(fp, mimetype)
        favicons_html.append(
            # prepare html with favicon data embedded
            '<link rel="icon" type="%s" sizes="%s" href="%s">\n'%(
                mimetype, sizes, data
            )
        )

    # modify header file
    with open(header_html_path,'r+') as hf:
        lines_in = hf.readlines() # read whole file
        lines_out = []
        for line in lines_in:
            # embed favicons into head
            if (line.startswith('</head>')):
                lines_out.extend(favicons_html)
            # remove all currently embedded favicons
            if (not (line.startswith('<link') and 'icon' in line)):
                lines_out.append(line)
        hf.seek(0) # move to beginning of file
        hf.write(''.join(lines_out)) # overwrite with new data
        hf.truncate() # remove trailing old data