Solasistim v3Solasistim v3http://www.solasistim.net//Solasistim v3ikiwiki2019-04-12T09:21:34ZQuick and dirty git-http on centoshttp://www.solasistim.net//posts/git_http_on_centos/2018-11-29T11:38:29Z2018-11-29T00:00:00Z
<h2>Setup procedure</h2>
<p>Install git, this provides the git-http-backend. <code>yum install git</code></p>
<p>Install httpd. <code>yum install httpd</code></p>
<pre><code>SetEnv GIT_PROJECT_ROOT /srv/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
&lt;Files "git-http-backend"&gt;
AuthType Basic
AuthName "Git Access"
AuthUserFile /srv/git/.htpasswd
Require expr !(%{QUERY_STRING} -strmatch '*service=git-receive-pack*' || %{REQUEST_URI} =~ m#/git-receive-pack$#)
Require valid-user
&lt;/Files&gt;
</code></pre>
<p>Create the git root and configure the users</p>
<pre><code>mkdir /srv/git
htpasswd -c /srv/git/.htpasswd db57
</code></pre>
<h2>Test procedure</h2>
<p>On the server:</p>
<pre><code>cd /srv/git
mkdir my-test-repository
cd my-test-repository
git init --bare
chown -R apache:apache /srv/git
</code></pre>
<p>On a client:</p>
<pre><code>mkdir my-test-repository
cd my-test-repository
git init
echo "test" &gt; README.md
git add -A
git commit -m 'initial import'
git remote add origin http://localhost/git/my-test-repository
git push -u origin master
</code></pre>
<p>As far as I know, all repositories need to be created server-side before you
are allowed to push to them.</p>
<h2>From outside</h2>
<p>Where the IP of your server is 10.179.127.226,</p>
<pre><code>git clone http://10.179.127.226/git/my-test-repository
</code></pre>
<p>Cloning is unauthenticated, only push is authenticated.</p>
Publishing a Vue component, 2018http://www.solasistim.net//posts/publishing_a_vue_component/2018-11-21T12:42:51Z2018-11-21T00:00:00Z
<p>An extremely minimal way to publish your Vue component. You already have your
project. I assume and hope you are using vue-cli 3.0. If not, immediately
switch to it, <code>vue create mypackage</code> and port your project into the new
structure.</p>
<p>To get the build for your project, you use the special <code>lib</code> <em>build target</em> to
the <code>vue-cli-service build</code> subcommand. You can invoke this as such:</p>
<pre><code>amoe@cslp019129 $ ./node_modules/.bin/vue-cli-service build --target lib --name amoe-butterworth-widgets src/components/TaxonSelect.vue
</code></pre>
<p>Here, <code>amoe-butterworth-widgets</code> is the name of the library that you intend to
publish it. In this case, I'm publishing it as an <a href="https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages">unscoped public
package</a>,
this is just the regular form of npm publishing that you all know and (hah)
love.</p>
<p><code>TaxonSelect.vue</code> will be exposed as the <em>default</em> export of the <a href="https://cli.vuejs.org/guide/build-targets.html#vue-vs-js-ts-entry-files">build
module</a>.</p>
<p>The build will produce several files under the <code>dist</code> subdirectory. You are
looking for the UMD build. You'll find a file
<code>dist/amoe-butterworth-widgets.umd.js</code>. Now you need to add a key to
<code>package.json</code>.</p>
<pre><code>{
...
"name": "amoe-butterworth-widgets",
"main": "dist/amoe-butterworth-widgets.umd.js",
"version": "0.1.0",
"license": "MIT",
...
}
</code></pre>
<p>It's wise to set a <a href="https://spdx.org/licenses/">license</a> and to obey semver as
appropriate.</p>
<p>Now you need to be logged in before you can actually publish. Run <code>npm
adduser</code>.</p>
<p>Once you've done this, simply run <code>npm publish</code>. Your package will be pushed
up and made available at <code>http://npm.js.com/package/mypackage</code>, where <code>mypackage</code>
was specified in the <code>name</code> field of <code>package.json</code>.</p>
<p>When someone runs <code>npm install mypackage</code>, they'll get what is more-or-less
a copy of your source tree. As far as I can see, npm doesn't attempt to clean
your tree or reproduce your build in any way. So make sure that anything you
don't want to be public is scrubbed before running <code>npm publish</code>.</p>
<p>When the user wants to actually use your component, <code>TaxonSelect.vue</code> is the
default export, as mentioned above. So to use it, they just type <code>import
TaxonSelect from 'mypackage'</code>, and <code>TaxonSelect</code>is then a component that they
can register in their <code>components</code> object. There are ways to export multiple
components from a module, but that's outside the scope of this article.</p>
Emoji Representationshttp://www.solasistim.net//posts/emoji_representations/2018-09-14T11:24:48Z2018-09-14T00:00:00Z
<p>Suppose you want to encode the Australian flag, you may consider this to be one
simple emoji character. Actually you're in for a surprise, perhaps, because
emojis aren't always represented as a single character, many emojis are
combinations of multiple Unicode code points.</p>
<p>For instance, the Australian flag may be represented as two 8-byte code points.</p>
<pre><code>U+0001F1E6
U+0001F1FA
</code></pre>
<p>This happens to display as a single glyph, the Australian flag, on some
platforms, but may also display as two separate glyphs.</p>
<p>However, some ways of representing text only support encoding of 4-byte code
points, those in the range U+0000 to U+FFFF. JSON is one of these. When we
attempt to escape the 8-byte characters (which, note, do not <strong>need</strong> to be
escaped under the JSON spec), we get a result that looks like two different
codepoints. Quoth RFC 7159:</p>
<pre><code>To escape an extended character that is not in the Basic Multilingual
Plane, the character is represented as a 12-character sequence,
encoding the UTF-16 surrogate pair. So, for example, a string
containing only the G clef character (U+1D11E) may be represented as
"\uD834\uDD1E".
</code></pre>
<p>So in fact, the Australian flag may be concretely represented in escaped JSON
as this string:</p>
<pre><code>"\ud83c\udde6\ud83c\uddfa"
</code></pre>
<p>As you can see, the last two hex digits of these escape pairs (<code>e6</code>, <code>fa</code>)
matches to the last bytes of the 8-byte code points above.</p>
Thoughts on Cheesesteak & Morehttp://www.solasistim.net//posts/thoughts_on_cheesesteak/2018-08-29T01:32:34Z2018-08-29T00:00:00Z
<p>I've been holding off a bit on the food posts of late, because when I enter the
kitchen these days it's often in a flurry of inspiration and I don't have the
wherewithal to fetch the camera. I have been continuing to document, though.
I've been repeating several recipes multiple times in different variations,
particularly the cheesesteak which I've probably made about 4 or 5 times by now.
I've tried it with various cuts, from the prohibitively expensive, authentic but
delicious rib eye, the wonderful and reasonably priced rump steak, and the
difficult-to-find skirt steak. I've also been experimenting with using beef
seasoning, particularly the Rajah brand, which deepens the flavour immensely.
It actually deepens the flavour so much I wonder if it might be too much, if it's
disguising the flavour of the cut itself. But I can't be sure without more
testing.</p>
<p>The biggest discovery that I made was to actually cook the steak properly as
steak rather than the sauted-beef method that's popular and probably more
authentic. This means the standard steak cooking method of browning in a hot
pan. I cook it until medium rare. You can feel free to do it as rare as you
like, because you can easily cook it more at the saute stage. Then remove from
the pan and deglaze, reserving the fond. You let the steak cool and slice it
into whatever consistency you like. I do chunky pieces. Then cook the rest
of the recipe with a covered pan and lastly add in the steak chunks with the
various juices. You'll have incredibly juicy cheesesteak mix and chunks that
yield to the teeth.</p>
<p>I've found that toasting the ciabatta lightly in the grill before putting in the
filling enhances the comfort-feeling of the sandwich. It's controversial but
I've started adding cold mayonnaise after the steak mixture. I think this adds
a lovely sharpness which offsets the giant umami hit from the brown stuff. This
does require a bit more care in adding the cheese, though, because you have to
ensure the cheese is melted properly before adding it to the sandwich, so you
need to somehow melt the cheese onto the steak mix before. I'm thinking of even
trying a lemony mayonnaise? Sounds crazy but perhaps it could work. I tried
gherkins as a condiment and found them rather disappointing, I really expected
the pickle flavour to work well but for some reason it didn't. Jalapenos work
better than gherkins, although I'm not sure I'd consider this a spicy sandwich.</p>
<p>I've also had some thoughts on <em>soto ayam</em>, having re-cooked it recently with
excellent results. Thigh works better than breast for this in my view, although
I'd certainly remove the skin next time. Two teaspoons of sambal ulek is enough
to cover about one bowl when eating with rice. I found out that I really don't
like the Maggi Malaysian sauce and need to stop eating it. However, the
notorious Maggi liquid seasoning does go very well with reduced-rice that's
destined for a South Asian dish, such as rice that you might drown in a big bowl
of <em>soto</em>. I can't tell what it is with these dishes but they just <em>go down</em>
with such aplomb, I feel like I could eat bowls and bowls of them without ever
being sated. This is a characteristic of both Owen's nasi goreng and Owen's
soto ayam, though curiously not the pangek ikan. I think the latter may be
because the fat content from the coconut milk tends to fill you up. The only
common characteristic between those two dishes that I can see is that they're
both lean and pungent, getting their kick from a vinegary sambal.</p>
<p>I got some experience in braising from attempting to cook a Mexican short rib in
adobo having been inspired by the menu at a local restaurant. But although I
succeeded in making edible food twice, both attempts were almost but not
entirely unlike the restaurant version, and both were utterly different from
each other to boot. The first was smoky, deep, but slightly bitter, having got
burned from excessively high oven heat and having the sauce scraped and melded
with it: I tempered it with some muscovado sugar, but it stayed questionable.
The second was the opposite: done on a low heat for an extremely long time,
it fell off the bone more satisfactorily than the first one, but doesn't seem
to have absorbed so much flavour in comparison to the first one. Perhaps
the truth lies in the middle. Who knew slow cooking could be so difficult?</p>
Vue + GraphQL + PostgreSQLhttp://www.solasistim.net//posts/vue_graphql_postgresql_integration/2018-07-20T09:27:13Z2018-07-20T00:00:00Z
<p>Goal: perform a basic query of our database through an autogenerated GraphQL
backend.</p>
<p>For this task we use Postgraphile, a tool modelled on Postgrest that generates
an API server based on introspecting the database schema.</p>
<p>We will also use the Apollo GraphQL client and the vue-apollo integration.<br />
These seem to be the most widely used libraries.</p>
<h2>Connecting to GraphQL</h2>
<p>In your main entry point, you can use this setup code:</p>
<pre><code>import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import VueApollo from 'vue-apollo';
const localApi = "/api";
const client = new ApolloClient({
link: new HttpLink({ uri: localApi }),
cache: new InMemoryCache()
});
const apolloProvider = new VueApollo({
defaultClient: client
});
Vue.use(VueApollo);
document.addEventListener("DOMContentLoaded", e =&gt; {
const vueInstance = new Vue({
render: h =&gt; h(ApplicationRoot),
provide: apolloProvider.provide()
});
vueInstance.$mount('#vue-outlet');
});
</code></pre>
<p>Your API will be available under the "/api" prefix to avoid CORS issues. You
configure this in webpack with the following stanza:</p>
<pre><code>devServer: {
port: 57242,
proxy: {
"/api": {
target: "http://localhost:5000/graphql",
pathRewrite: {"^/api": ""}
}
}
}
</code></pre>
<p>The Postgraphile server will appear on port 5000 by default.</p>
<h2>Starting the server</h2>
<p>This is extremely simple.</p>
<pre><code>amoe@klauwier $ sudo yarn global add postgraphile
amoe@klauwier $ postgraphile -c postgres://localhost/mol_viewer
PostGraphile server listening on port 5000 🚀
‣ Connected to Postgres instance postgres://localhost:5432/mol_viewer
‣ Introspected Postgres schema(s) public
‣ GraphQL endpoint served at http://localhost:5000/graphql
‣ GraphiQL endpoint served at http://localhost:5000/graphiql
* * *
</code></pre>
<p>Your database here is <code>mol_viewer</code>. A service should start on port 5000.</p>
<h2>Writing a sample query</h2>
<p>Using the module <code>graphql-tag</code>, you can define your queries using template
literals. This will syntax-check your queries at compile time.</p>
<pre><code>const demoQuery = gql`
{
allParticipants {
nodes {
reference
}
}
}`
</code></pre>
<p>This looks slightly weird, but the important thing to know here is that I have a
table already in my database schema called <code>participant</code>. Postgraphile has
inferred a collection of objects, therefore, called <code>allParticipants</code>. My table
has a field called <code>reference</code> (which can be of any type). <code>nodes</code> is (I believe)
a Postgraphile-specific property of the list <code>allParticipants</code>. That is to say,
the text <code>nodes</code> above has nothing to do with the database schema of <code>mol_viewer</code>,
rather it's an artifact of using Postgraphile.</p>
<h2>Link the query to your page</h2>
<p>When you have set up <code>vue-apollo</code>, you have access to a <code>apollo</code> property on your
Vue instance. In this case it looks as such.</p>
<pre><code>apollo: {
allParticipants: demoQuery
}
</code></pre>
<p>Here, <code>allParticipants</code> is a name which must be the same as the top-level result
of the query. It's not just an arbitrary identifier.</p>
<p>Now, <code>allParticipants</code> field exists in your component's data object. The query
is automatically made when you load your page, and <code>allParticipants</code> will be
populated. You can demonstrate this through the simple addition of
<code>{{allParticipants}}</code> to your template.</p>
<p>To iterate through the query results, you have to consider that the results of
the query are always shaped like the query itself. So your basic list of the
results will look as follows.</p>
<pre><code>&lt;ol&gt;
&lt;li v-for="node in allParticipants.nodes"&gt;
{{node.reference}}
&lt;/li&gt;
&lt;/ol&gt;
</code></pre>
<h2>Filtering</h2>
<p>Postgraphile supports filtering using the simple <code>condition</code> on the
auto-generated <code>allParticipants</code> field. For instance, imagine filtering by a
given reference. You'd do <code>allParticipants(condition: {reference: "A14"})</code>.
Only simple equality is supported out of the box (!). Plugins are available.</p>
SCons: build header with version number, out-of-source buildhttp://www.solasistim.net//posts/scons_build_version_number/2018-06-08T14:17:12Z2018-06-08T00:00:00Z
<p>This is a bit tricky because multiple ways of doing it are documented. This
is the way that eventually worked for me.</p>
<p>The top-level <code>SConstruct</code> is as normal for an out-of-source build, it reads</p>
<pre><code>SConscript('src/SConscript', variant_dir='build')
</code></pre>
<p>You need a header so that your program can recognize the version number. In
C++ this is as follows, in <code>src/version.hh</code>:</p>
<pre><code>extern char const* const version_string;
</code></pre>
<p>You can define the version that you want to update in a file named <code>version</code>
which is in the root of the repository. It should have no other content other
than the version number, perhaps along with a newline.</p>
<pre><code>0.0.1
</code></pre>
<p>Now the <code>src/SConscript</code> file should look like this:</p>
<pre><code>env = Environment()
# The version file is located in the file called 'version' in the very root
# of the repository.
VERSION_FILE_PATH = '#version'
# Note: You absolutely need to have the #include below, or you're going to get
# an 'undefined reference' message due to the use of const. (it's the second
# const in the type declaration that causes this.)
#
# Both the user of the version and this template itself need to include the
# extern declaration first.
def version_action(target, source, env):
source_path = source[0].path
target_path = target[0].path
# read version from plaintext file
with open(source_path, 'r') as f:
version = f.read().rstrip()
version_c_text = """
#include "version.hh"
const char* const version_string = "%s";
""" % version
with open(target_path, 'w') as f:
f.write(version_c_text)
return 0
env.Command(
target='version.cc',
source=VERSION_FILE_PATH,
action=version_action
)
main_binary = env.Program(
'main', source=['main.cc', 'version.cc']
)
</code></pre>
<p>The basic strategy here is to designate the <code>version</code> file as the source file
for <code>version.cc</code>, but we just hardcode the template for the actual C++
definition inside the SConscript itself. Note that the include within the
template is crucial, due to an <a href="https://stackoverflow.com/questions/14894698/">'aspect' of the C++ compilation
process</a>.</p>
Vue + Leaflet + Webpack + TypeScripthttp://www.solasistim.net//posts/vue_leaflet_typescript/2018-05-22T15:06:45Z2018-05-22T00:00:00Z
<p>This is really tricky. There are several hurdles you face.</p>
<p>First hurdle: importing the Leaflet CSS files from your <code>node_modules</code> folder
and incorporating this into your Webpack build.</p>
<p>The canonical form for this is as follows:</p>
<pre><code>@import url("~leaflet/dist/leaflet.css");
</code></pre>
<p>The tilde is a documented but obscure shortcut for a vendored module found under
<code>node_modules</code>. There's no way to avoid hardcoding the path <code>dist/leaflet.css</code>.</p>
<p>Once you've done this, you'll have a non-broken map view, but you still won't
be able to view marker images. You'll be seeing that the CSS attempts to load
images but isn't able to load them. Then you'll try to apply <code>file-loader</code>, but
due to a similar issue to <a href="https://github.com/PaulLeCam/react-leaflet/issues/255">one described on React</a>,
you'll note that <code>file-loader</code> or <code>url-loader</code> generate broken paths with
strange hashing symbols in them.</p>
<p>Luckily, there's a fix for this! You'll notice this solution in the thread,
from user PThomir:</p>
<pre><code>import L from 'leaflet';
L.Icon.Default.imagePath = '.';
// OR
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});
</code></pre>
<p>This is now getting very close. However, you'll try to adapt this, using
<code>import</code> instead of <code>require</code>, because TypeScript doesn't know about <code>require</code>.</p>
<p>You'll get examples like this:</p>
<pre><code>Cannot find module 'leaflet/dist/images/marker-icon-2x.png'
</code></pre>
<p>But you'll look for the file and it'll clearly be there. Puzzling. Until you
realize you've missed a key point: <em>Webpack's <code>require</code> and TypeScript's <code>import</code>
are completely different animals</em>. More specifically: <strong>Only Webpack's <code>require</code>
knows about Webpack's loaders.</strong> So when you might try to import the PNG,</p>
<pre><code>import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
</code></pre>
<p>This is actually intercepted by the TypeScript compiler and causes a compile
error. We need to find some way to use Webpack's <code>require</code> from typescript.
Luckily this isn't too difficult. You need to create a type signature for this
call as such.</p>
<pre><code>// This is required to use Webpack loaders, cf https://stackoverflow.com/a/36151803/257169
declare function require(string): any;
</code></pre>
<p>Put this somewhere in your search path for modules, as <code>webpack-require.d.ts</code>.
Remember you don't explicitly import <code>.d.ts</code> file. So now just use <code>require</code>
in your <code>entry.ts</code> file as before.</p>
<p>My eventual snippet looked as follows:</p>
<pre><code>const leaflet = require('leaflet');
delete leaflet.Icon.Default.prototype._getIconUrl;
const iconRetinaUrl = require('leaflet/dist/images/marker-icon-2x.png');
const iconUrl = require('leaflet/dist/images/marker-icon.png');
const shadowUrl = require('leaflet/dist/images/marker-shadow.png');
leaflet.Icon.Default.mergeOptions({ iconRetinaUrl, iconUrl, shadowUrl })
</code></pre>
<p>But remember, none of this will work without that <code>.d.ts</code> file, otherwise <code>tsc</code>
is just going to wonder what the hell you mean by <code>require</code>.</p>
Neo4j Cypher query to NetworkXhttp://www.solasistim.net//posts/neo4j_to_networkx/2018-05-11T09:20:55Z2018-05-09T00:00:00Z
<p>The basic question is, how do we read an entire graph from a Neo4j store into a
NetworkX graph? And another question is, how do we extract subgraphs from
Cypher and recreate them in NetworkX, to potentially save memory?</p>
<h2>Using a naive query to read all relationships</h2>
<p>This is based on cypher-ipython module. This uses a simple query like the
following to obtain all the data:</p>
<pre><code>MATCH (n) OPTIONAL MATCH (n)-[r]-&gt;() RETURN n, r
</code></pre>
<p>This can be read into a graph using the following code. Note that the rows may
duplicate both relationships and nodes, but this is taken care of by the use of
neo4j IDs.</p>
<pre><code>def rs2graph(rs):
graph = networkx.MultiDiGraph()
for record in rs:
node = record['n']
if node:
print("adding node")
nx_properties = {}
nx_properties.update(node.properties)
nx_properties['labels'] = node.labels
graph.add_node(node.id, **nx_properties)
relationship = record['r']
if relationship is not None: # essential because relationships use hash val
print("adding edge")
graph.add_edge(
relationship.start, relationship.end, key=relationship.type,
**relationship.properties
)
return graph
</code></pre>
<p>There's something about this query that is rather inelegant, that is that the
result set is essentially 'denormalized'.</p>
<h2>Using aggregation functions</h2>
<p>Luckily there's another more SQL-ish way to do it, which is to COLLECT the
relationships of each node into an array. This then returns lists which
represent a distinct node and the complete set of relationships for that node,
similar to something like the <code>ARRAY_AGG()</code> and <code>GROUP BY</code> combination in
PostgreSQL. This seems much cleaner to me.</p>
<pre><code># this version expects a collection of rels in the variable 'rels'
# But, this version doesn't handle dangling references
def rs2graph_v2(rs):
graph = networkx.MultiDiGraph()
for record in rs:
node = record['n2']
if not node:
raise Exception('every row should have a node')
print("adding node")
nx_properties = {}
nx_properties.update(node.properties)
nx_properties['labels'] = list(node.labels)
graph.add_node(node.id, **nx_properties)
relationship_list = record['rels']
for relationship in relationship_list:
print("adding edge")
graph.add_edge(
relationship.start, relationship.end, key=relationship.type,
**relationship.properties
)
return graph
</code></pre>
<h2>Trying to extend to handle subgraphs</h2>
<p>When we have relationship types that define subtrees, which are labelled
something like <code>:PRECEDES</code> in this case, we can attempt to materialize this
sub-graph selected from a given root in memory. In the query below, the <code>Token</code>
node with content <code>nonesuch</code> is taken as the root.</p>
<p>This version can be used with a Cypher query like the following:</p>
<pre><code>MATCH (a:Token {content: "nonesuch"})-[:PRECEDES*]-&gt;(t:Token)
WITH COLLECT(a) + COLLECT(DISTINCT t) AS nodes_
UNWIND nodes_ AS n
OPTIONAL MATCH p = (n)-[r]-()
WITH n AS n2, COLLECT(DISTINCT RELATIONSHIPS(p)) AS nestedrel
RETURN n2, REDUCE(output = [], rel in nestedrel | output + rel) AS rels
</code></pre>
<p>And the Python code to read the result of this query is as such:</p>
<pre><code># This version has to materialize the entire node set up front in order
# to check for dangling references. This may induce memory problems in large
# result sets
def rs2graph_v3(rs):
graph = networkx.MultiDiGraph()
materialized_result_set = list(rs)
node_id_set = set([
record['n2'].id for record in materialized_result_set
])
for record in materialized_result_set:
node = record['n2']
if not node:
raise Exception('every row should have a node')
print("adding node")
nx_properties = {}
nx_properties.update(node.properties)
nx_properties['labels'] = list(node.labels)
graph.add_node(node.id, **nx_properties)
relationship_list = record['rels']
for relationship in relationship_list:
print("adding edge")
# Bear in mind that when we ask for all relationships on a node,
# we may find a node that PRECEDES the current node -- i.e. a node
# whose relationship starts outside the current subgraph returned
# by this query.
if relationship.start in node_id_set:
graph.add_edge(
relationship.start, relationship.end, key=relationship.type,
**relationship.properties
)
else:
print("ignoring dangling relationship [no need to worry]")
return graph
</code></pre>
Deployment of GitLab on Ubuntu xenialhttp://www.solasistim.net//posts/gitlab_ubuntu_xenial/2018-05-10T16:11:00Z2018-03-09T00:00:00Z
<p>This is something of a pain in the arse, there are several main points to
remember.
These points apply to the version <code>8.5.8+dfsg-5</code>, from Ubuntu <code>universe</code>.</p>
<p>Note that the Debian-derived <code>gitlab</code> package has been REMOVED from Debian
bionic.</p>
<h2>Install the packages from universe</h2>
<h2>Work around bug 1574349</h2>
<p>You'll come across a bug: https://bugs.launchpad.net/ubuntu/+source/gitlab/+bug/1574349</p>
<p>The tell-tale sign of this bug is a message about a gem named
<code>devise-two-factor</code>. As far as I can tell, there's no way to work around this
and stay within the package system.</p>
<p>You have to work around this, but first:</p>
<h3>Install bundler build dependencies</h3>
<pre><code>apt install cmake libmysqlclient-dev automake autoconf autogen libicu-dev pkg-config
</code></pre>
<h3>Run bundler</h3>
<p>Yes, you're going to have to install gems outside of the package system.</p>
<ul>
<li><code># cd /usr/share/gitlab</code></li>
<li><code># bundler</code></li>
</ul>
<p>And yes, this is a bad situation.</p>
<h2>Unmask all gitlab services</h2>
<blockquote><p>[Masking] one or more units... [links] these unit files to /dev/null, making it
impossible to start them.</p></blockquote>
<p>For some reason the apt installation process installs all the gitlab services
as masked. No idea why but you'll need to unmask them.</p>
<pre><code>systemctl unmask gitlab-unicorn.service
systemctl unmask gitlab-workhorse.service
systemctl unmask gitlab-sidekiq.service
systemctl unmask gitlab-mailroom.service
</code></pre>
<h2>Interactive authentication required</h2>
<p>You're going to face this error, too. You need to create an override so that
gitlab gets started with the correct user. You can do that with <code>systemctl edit
gitlab</code>, this will create a local override.</p>
<p>Insert this in the text buffer:</p>
<pre><code>[Service]
User=gitlab
</code></pre>
<p>Save and quit and now you need to reload &amp; restart.</p>
<pre><code>systemctl daemon-reload
systemctl start gitlab
</code></pre>
<h2>Purging debconf answers</h2>
<p>Since gitlab is sometimes an interactively configured package, sometimes stale
information can get stored in the debconf database, which will hinder you.
To clear this out and reset them, do the following:</p>
<pre><code>debconf-show gitlab
echo PURGE | debconf-communicate gitlab
</code></pre>
<p>This is the first time I've had to learn about this in a good 10 years of using
and developing Debian-derived distributions. That's how successful an abstraction
debconf is.</p>
<p>Update 2018-05-04: Also pin <code>ruby-mail</code> package to artful version 2.6.4+dfsg1-1</p>
FP & the 'Context Problem'http://www.solasistim.net//posts/fp_and_the_context_problem/2018-08-14T17:05:37Z2018-02-27T00:00:00Z
<p>[Originally written 2017-09-22. I don't have time to finish this post now, so I
might as well just publish it while it's still not rotted.]</p>
<p>While coding large backend applications in Clojure I noticed a pattern that
continued to pop up.</p>
<p>When learning FP initially, you initially learn the basics: your function should
not rely on outside state. It should not mutate it, nor observe it, unless it's
explicitly passed in as an argument to the function. This rule generally includes
mutable resources in the same namespace, e.g. an atom, although constant values
are still allowed. Any atom that you want to access must be passed in to the
function.</p>
<p>Now, this makes total sense at first, and it allows us to easily implement the
pattern described in Gary Bernhardt's talk "Boundaries", of "Functional Core,
Imperative Shell" [FCIS]. This means that we do all I/O <em>at</em> the boundaries.</p>
<pre><code>(defn sweep [db-spec]
(let [all-users (get-all-users db-spec)]
(let [expired-users (get-expired-users all-users)]
(doseq [user expired-users]
(send-billing-problem-email! user)))))
</code></pre>
<p>This is a translation of Gary's example. A few notes on this implementation.</p>
<ol>
<li><code>sweep</code> as a whole is considered part of the imperative shell.</li>
<li><code>get-all-users</code> and <code>send-billing-problem-email!</code> are what we'll loosely
refer to as "boundary functions".</li>
<li><code>get-expired-users</code> is the "functional core".</li>
</ol>
<p>The difference that Gary stresses is that the <code>get-expired-users</code> function
contains all the <strong>decisions</strong> and no <strong>dependencies</strong>. That is, all the
conditionals are in the <code>get-expired-users</code> function. That function purely
operates on a data in, data out basis: it knows nothing about I/O.</p>
<p>This is a small-scale paradigm shift for most hackers, who are used to
interspersing their conditionals with output; consider your typical older-school
PHP bespoke system, which is bursting with DB queries that have their result
spliced directly into pages. But, this works very well for this simple example.
It accomplishes the goal of making everything testable pretty well. And you'd
be surprised how far overall this method can take you.</p>
<p>It formalizes as this: Whenever you have a function that intersperses I/O with
logic, separate out the logic and the I/O, and apply them separately. This is
usually harder for output than for input, but it's usually possible to construct
some kind of data representation of what output operation should in fact be
effected -- what I'll call an "output command" -- and pipe that data to a "dumb"
driver that just executes that command.</p>
<p>You can reconstruct most procedures in this way. The majority of problems,
particularly in a backend REST system, break down to "do some input operation",
"run some logic", "do some output operation". Here I'm referring to the
database as the source and target of IO. This is the 3-tier architecture
described by Fowler in PoEAA.</p>
<p>However, you probably noticed an inefficiency in the code above. Likely we get
all users and then decide within the language runtime whether a given user is
expired or not. We've given up the ability of the database to answer this
question for us. Now we're reading the entire set of users into memory, and
mapping them to objects, before we make any decision about whether they're
expired or not.</p>
<p>Realistically, this isn't likely to be a problem, depending on the number of
users. Obviously Gmail is going to have a problem with this approach. But
surely you're fine until perhaps 10,000 users, assuming that your mapping code
is relatively efficient.</p>
<p>Anyway, this isn't the problem that led me to discover this. The problem
happened when I was implementing the basics of the REST API, and attempting to
be as RESTfully-correct as possible, I wanted to use linking. This seems easy,
when you only need to produce internal links, right? In JSON, we chose a certain
representation (the Stormpath representation).</p>
<pre><code>GET /users/1
{
"name": "Dave",
"pet": {"href": &lt;url&gt;},
"age": 31
}
</code></pre>
<p>Now, assume we also have a separate resource for a user's pet. In REST, that's
represented by the URL <code>/pets/1</code> for a pet with identifier <code>1</code>. We have the
ability to indicate this pet through either relative or absolute URLs. Assume
that our base URL for the API is <code>https://cool-pet-tracker.solasistim.net/api</code>.</p>
<ul>
<li>The relative URL is <code>/pets/1</code>.</li>
<li>The absolute URL is <code>https://cool-pet-tracker.solasistim.net/api</code>.</li>
</ul>
<p>If you search around a bit, you'll find that from what small amount of consensus
exists, REST URLs that get returned are always required to be absolute. This
pretty much makes sense, given that a link represents a concrete resource that
is available at a certain point in time, in the sense of "Cool URLs Don't
Change".</p>
<p>Now the problem becomes, say we have a function that attempt to implement the
<code>/users/:n</code> API. We'll write this specifically NOT in the FCIS style, so we'll
entangle the I/O. (Syntax is specific to Rook.)</p>
<pre><code> (defn show [id ^:injection db-spec]
(let [result (get-user db-spec {:user (parse-int-strict id)})]
{:name (:name result)
:pet nil
:age (:age result)}))
</code></pre>
<p>You'll notice that I left out the formation of the link. Let's add the link.</p>
<pre><code> (defn show [id request ^:injection db-spec]
(let [result (get-user db-spec {:user (parse-int-strict id)})]
{:name (:name result)
:pet (make-rest-link request "/pet" (:pet_id result))
:age (:age result)}))
</code></pre>
<p>Now, we define <code>make-rest-link</code> naively as something like this.</p>
<pre><code> (defn make-rest-link [request endpoint id]
(format "%s/%s/%s" (get-in request [:headers "host"])
endpoint
id))
</code></pre>
<p>Yeah, there's some edges missed here but that's the gist of it. The point is
that we use whatever Host URI was requested to send back the linked result.
[This has some issues with reverse proxy servers that sometimes calls for a more
complicated solution, but that's outside the scope of this document.]</p>
<p>Now did you notice the issue? We had to add the <code>request</code> to the signature of
the function. Now, that's pretty much a small deal in this case: the use of the
<code>request</code> is a key part of the function's purpose, and it makes sense for every
function to have knowledge of it. But just imagine that we were dealing with a
deeply nested hierarchy.</p>
<pre><code>(defn form-branch []
{:something-else 44})
(defn form-tree []
{:something-else 43
:branch (form-branch)})
(defn form-hole [id]
{:something 42
:tree (form-tree)})
(defn show [id ^:injection db-spec]
(form-hole id))
</code></pre>
<p>As you can see, this is a nested structure: a hole has a tree and that tree
itself has a branch. That's fine so far, but we don't really want to go any
deeper than 3 layers. Now, the branch gets a "limb" (this is a synonym for
"bough", a large branch). But we only want to put a link to it.</p>
<pre><code>(defn form-limb [request]
(make-rest-link request "/limbs" 1))
(defn form-branch [request]
{:something-else 44
:limb (form-limb request)})})
(defn form-tree [request]
{:something-else 43
:branch (form-branch request)})
(defn form-hole [id request]
{:something 42
:tree (form-tree request)})
(defn show [id request ^:injection db-spec]
(form-hole id request))
</code></pre>
<p>Now we have a refactoring nightmare. All of the intermediate functions, that
mirror the structure of the entity, had to be updated to know about the request.
Even though they themselves did not examine the request at all. This isn't bad
just because of the manual work involved: it's bad because it clouds the intent
of the function.</p>
<p>Now anyone worth their salt will be thinking of ways to improve this. We could
cleverly invert control and represent links as functions.</p>
<pre><code>(defn form-branch []
{:something-else 44
:limb #(make-rest-link % "/limbs" 1)})
</code></pre>
<p>Then, though, we need to run over the entire structure before coercing it to
REST and specially treat any functions. This could be accomplished using
<code>clojure.walk</code> and it would probably work OK.</p>
<p>What's actually being required here? What's happened is a function deep in the
call stack has a need for context that's only available in the outside of the
stack. But, that information is really only peripheral to its purpose. As you
can see we were able to form an adequate representation of the link as a function,
which by no means obscures its purpose from the reader. If anything the purpose
is clearer.</p>
<p>This problem can also pop up in other circumstances that seem less egregious. In
general, any circumstance where you need to use I/O for a small part of the result
at a deep level in the stack will result in a refactoring cascade as all intervening
functions end up with added parameters. There are several ways to ameliorate this.</p>
<h2>1: The "class" method</h2>
<p>This method bundles up the context with the functionality as a record. The context
then becomes referrable to by any function within that protocol.</p>
<pre><code>(defprotocol HoleShower
(show [id] "Create JSON-able representation of the given hole."))
(defrecord SQLHoleShower [request db-spec]
HoleShower
(show [this id]
{:something 42
:tree (form-tree id)})
(form-tree [this id]
{:something-else 44
:branch (make-rest-link request "/branches" 1)}))
</code></pre>
<p>As you can see, we don't need to explicitly pass <code>request</code> because every instance
of an SQLHoleShower automatically has access to the <code>request</code> that was used to
construct it. However, it has the very large downside that these functions
then become untestable outside of the context of an SQLHoleShower. They're
defined, but not that useful.</p>
<h2>2. The method of <code>maker</code></h2>
<p>This is a library by <a href="https://github.com/tamasjung/maker">Tamas Jung</a> that
implements a kind of implicit dependency resolution algorithm. Presumably it's
a topo-sort equivalent to the <code>system</code> logic in Component.</p>
<pre><code>(ns clojure-playground.maker-demo
(:require [maker.core :as maker]))
(def stop-fns (atom (list)))
(def stop-fn
(partial swap! stop-fns conj))
(maker/defgoal config []
(stop-fn #(println "stop the config"))
"the config")
;; has the more basic 'config' as a dependency
;; You can see that 'defgoal' actually transparently manufactures the dependencies.
;; After calling (make db-conn), (count @stop-fns) = 2:
;; that means that both db-conn AND its dependency config were constructed.
(maker/defgoal db-conn [config]
(stop-fn #(println "stop the db-conn"))
(str "the db-conn"))
;; This will fail at runtime with 'Unknown goal', until we also defgoal `foo`
(maker/defgoal my-other-goal [foo]
(str "somthing else"))
</code></pre>
<p>The macro <code>defgoal</code> defines a kind of second class 'goal' which is only known
about by the maker machinery. When a single item anywhere in the graph is
"made" using the <code>make</code> function, the library knows how to resolve all the
intermediaries. It's kind of isomorphic to the approach taken by Claro,
although it relies on more magical macrology.</p>
<p>https://www.niwi.nz/2016/03/05/fetching-and-aggregating-remote-data-with-urania/
https://github.com/kachayev/muse
https://github.com/facebook/Haxl
https://www.youtube.com/watch?v=VVpmMfT8aYw</p>
<p>See this:
Retaking Rules for developers:
https://www.youtube.com/watch?v=Z6oVuYmRgkk&amp;feature=youtu.be&amp;t=9m54s</p>
<p>And of course, the "Out of the Tar Pit" paper.</p>
<p>Update 2018-08-14: Two other solutions to this broad problem are the Reader
monad (see this great <a href="https://passy.svbtle.com/dont-fear-the-reader">Pascal Hartig
article</a> and what Sinclair refers
to as the <a href="https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/">Effect
functor</a>.</p>
Cloake Vegetable Biryanihttp://www.solasistim.net//posts/cloake_vegetable_biryani/2018-02-27T12:28:07Z2018-02-25T00:00:00Z
<p>The most interesting thing about this piece is the butter/saffron/milk mixture,
pictured below. This turns out delicately spiced and exotic, with a kind of
floral note from the cardamom. I don't know how you're supposed to eat it: for
me, I may have failed immediately by not using sufficient rice.</p>
<p>I don't really buy the pastry rim, as pictured here. This is supposed to create
a more tight seal on the dish while it's in the oven. But it seems like a bit
of a waste; the visual presentation is wonderful, though.</p>
<p>I found it interesting to read, in the history of the biryani, that beef biryani
is a favourite in Kerala. This would be a nice next try.</p>
<p>I deviated from the recipe by not including cauliflower, this was unintentional.
I'd say the overall result is dominated by the chana dal. It has that kind of
'grainy' taste associated with a dal.</p>
<p>In a way, I can't agree that this is 'perfect': in a way, it's too subtle for
me. The flavours don't quite punch through enough. I think an addition of
deep-fried onions, another addition from Cloake that I couldn't add, would
improve it. Ironically this is kind of the opposite of Cloake's chilli, that I
made previously, which was if anything too pungent.</p>
<p>Actually I'd say the flavour issue with this is the sour balance. It's just a
touch too sour in a way that's not mitigated by the other flavours. I suppose
you have to remember that yoghurt is sour. I think that I may have overdone
the amount of yoghurt in this recipe. It was supposed to be 200ml of yoghurt
for ~700g of main ingredient, but I have probably done about 500ml for 500g
instead. And then also added lime juice on top of this. Cloake actually
anticipates that the yoghurt will be insufficient and recommends diluting it.
So the lesson here is to go for the balance of 1/4 yoghurt to main ingredient ratio.</p>
<p>This means that you'd normally want to buy smaller portions of yoghurt, about
250ml containers, and thin them with water or milk when you want to marinate.
And perhaps leave the addition of other souring agents to later, when you
already use yoghurt in a curry.</p>
<p><a href="http://www.solasistim.net//images/P1020013.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020013.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020014.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020014.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020015.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020015.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020016.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020016.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020017.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020017.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020018.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020018.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020019.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020019.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020020.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020020.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020021.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020021.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020022.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020022.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020023.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020023.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020024.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020024.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020025.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020025.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/P1020026.jpg"><img src="http://www.solasistim.net//posts/cloake_vegetable_biryani/200x200-P1020026.jpg" width="200" height="150" class="img" /></a></p>
Deploying Qt applications for Machttp://www.solasistim.net//posts/deploying_qt_applications_for_mac/2018-03-09T16:27:24Z2018-02-19T00:00:00Z
<p>Deploying desktop applications on a Mac, for us Linux guys, can be strange. I
didn't even know what the final deliverable file was.</p>
<p>Well, that file is the DMG, or Apple Disk Image.</p>
<p>To create the DMG, you first need to create an intermediate result, the App
Bundle.</p>
<p>I assume you're using Scons as your build system. If you're not, which
admittedly is quite likely, then go and read another post.</p>
<p>To create the App Bundle you can use an Scons tool from a long time ago which
doesn't seem to have a real home at the moment. It'd be a good project to try
to rehabilitate this.</p>
<p>In the meantime, I've created a <a href="https://gist.github.com/amoe/589dc2aad649d574689b7a1211c8fb56">Gist</a>
that contains the code for that. Download it and put it in the same directory
as your <code>SConstruct</code>.</p>
<p>To use it, you have to bear in mind that it's going to overwrite your environment
quite heavily. So I suggest using a totally new environment for it.</p>
<p>Your final SConstruct is going to look something like this:</p>
<pre><code># SConstruct
import os
from osxbundle import TOOL_BUNDLE
def configure_qt():
qt5_dir = os.environ.get('QT5_DIR', "/usr")
env = Environment(
tools=['default', 'qt5'],
QT5DIR=qt5_dir
)
env['QT5_DEBUG'] = 1
maybe_pkg_config_path = os.environ.get('PKG_CONFIG_PATH')
if maybe_pkg_config_path:
env['ENV']['PKG_CONFIG_PATH'] = maybe_pkg_config_path
env.Append(CCFLAGS=['-fPIC', '-std=c++11'])
env.EnableQt5Modules(['QtCore', 'QtWidgets', 'QtNetwork'])
return env
# A Qt5 env is used to build the program...
env = configure_qt()
env.Program('application', source=['application.cc'])
# ... but a different env is needed in order to bundle it.
bundle_env = Environment()
TOOL_BUNDLE(bundle_env)
bundledir = "the_bundle.app"
app = "application" # The output object?
key = "foobar"
info_plist = "info_plist.xml"
typecode = 'APPL'
bundle_env.MakeBundle(bundledir, app, key, info_plist, typecode=typecode)
</code></pre>
<p>As an explanation of these arguments, <code>bundledir</code> is the output directory, which
must always end in <code>.app</code>. <code>app</code> is the name of your executable program (the
result of compiling the C++ <code>main</code> function). <code>key</code> is unclear, some other
context suggests that it's used for a Java-style reversed domain organization
identifier, such as <code>net.solasistim.myapplication</code>.</p>
<p>You can also provide <code>icon_file</code> (a path) and <code>resources</code> (a list) which are then
folded into the <code>/Contents/Resources</code> path inside the .app.</p>
<p>Once you've got your <code>.app</code> you now need to create a DMG file. Like this.</p>
<pre><code>$ macdeployqt the_bundle.app -dmg
</code></pre>
<p>You should now find <code>the_bundle.dmg</code> floating in your current directory. Nice.</p>
Simple CSS for the Stupid: Activity Spinnerhttp://www.solasistim.net//posts/simple_css_spinner/2018-02-05T12:41:57Z2018-02-05T00:00:00Z
<p>How simple can a spinner be, and still be devoid of hacks? Let's see:</p>
<p>Markup in a Vue component, using <code>v-if</code> to hide and show:</p>
<pre><code>&lt;svg v-if="inProgressCount &gt; 0" height="3em" width="3em" class="spinner"&gt;
&lt;circle cx="50%"
cy="50%"
r="1em"
stroke="black"
stroke-width="0.1em"
fill="#001f3f" /&gt;
&lt;/svg&gt;
</code></pre>
<p>The CSS to create the animation, and "pin" it so that it's always visible:</p>
<pre><code>svg.spinner {
position: fixed;
left: 0px;
top: 0px;
}
svg.spinner circle {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% {
fill: #001f3f;
}
50% {
fill: #ff4136;
}
100% {
fill: #001f3f;
}
}
</code></pre>
<p>The only thing that I don't understand here is why it's necessary to list
duplicate fill values for <code>0%</code> and <code>100%</code>. That's needed to create a proper
loop. Answers on a postcard.</p>
FFXII Buildshttp://www.solasistim.net//posts/ffxii_builds/2018-02-02T11:00:10Z2018-02-02T00:00:00Z
<p>To record my FFXII builds pre Act 8.</p>
<h2>Vaan - Time Battlemage / Monk</h2>
<p>Penetrator Crossbow, Lead Shot, Giant's Helmet, Carabineer Mail, Hermes Sandals</p>
<h2>Balthier - White Mage / Machinist</h2>
<p>Spica, Celebrant's Miter, Cleric's Robes, Sage's Ring</p>
<h2>Fran - Archer / Uhlan</h2>
<p>Yoichi Bow, Parallel Arrows, Giant's Helmet, Carabineer Mail, Sash</p>
<h2>Basch - Knight / Foebreaker</h2>
<p>Save the Queen, Dragon Helm, Dragon Mail, Power Armlet</p>
<h2>Ashe - Red Battlemage / Bushi</h2>
<p>Ame-no-Murakumo, Celebrant's Miter, Cleric's Robes, Nishijin Belt</p>
<h2>Penelo - Black Mage / Shikari</h2>
<p>Platinum Dagger, Aegis Shield, Celebrant's Miter, Cleric's Robes, Sash</p>
Traversing Word documents with docx4javahttp://www.solasistim.net//posts/docx4java/2018-01-04T17:06:18Z2018-01-04T00:00:00Z
<p>Sometimes you may need to extract content from a word document. You <em>will</em> need
to be aware of the structure. Extremely simplified, a Word document has the
following structure:</p>
<ol>
<li>At the top level is a list of "parts".</li>
<li>One part is the "main document part", <code>m</code>.</li>
<li>The part <code>m</code> contains some <code>w:p</code> elements, represented in Docx4j as
<code>org.docx4j.wml.P</code> objects. Semantically this represents a paragraph.</li>
<li>Each paragraph consists of "runs" of text. These are <code>w:r</code> elements. I
think that the purpose of these is to allows groups within paragraphs to
have individual stylings, roughly like <code>span</code> in HTML.</li>
<li>Each run contains <code>w:t</code> elements, or <code>org.docx4j.wml.Text</code>. This contains
the meat of the text.</li>
</ol>
<p>Here's how you define a traversal against a Docx file:</p>
<pre><code>public class TraversalCallback extends TraversalUtil.CallbackImpl {
@Override
public List&lt;Object&gt; apply(Object o) {
if (o instanceof org.docx4j.wml.Text) {
org.docx4j.wml.Text textNode = (org.docx4j.wml.Text) o;
String textContent = textNode.getValue();
log.debug("Found a string: " + textContent);
root.appendChild(element);
} else if (o instanceof org.docx4j.wml.Drawing) {
log.warn("FOUND A DRAWING");
}
return null;
}
@Override
public boolean shouldTraverse(Object o) {
return true;
}
}
</code></pre>
<p>Note that we inherit from TraversalUtil.CallbackImpl. This allows us to avoid
implementing <code>walkJAXBElements()</code> method ourselves -- although you still might
need to, if your algorithm can't be defined in the scope of the <code>apply</code> method.
It seems like the return value of <code>apply</code> is actually ignored by the superclass
implementation of <code>walkJAXBElements</code>, so you can just return NULL.</p>
<p>To bootstrap it from a file, you just do the following:</p>
<pre><code>URL theURL = Resources.getResource("classified/lusty.docx");
WordprocessingMLPackage opcPackage = WordprocessingMLPackage.load(theURL.openStream());
MainDocumentPart mainDocumentPart = opcPackage.getMainDocumentPart();
TraversalCallback callback = new TraversalCallback();
callback.walkJAXBElements(mainDocumentPart);
</code></pre>
<p>By modifying the <code>apply</code> method, you can special-case each type of possible
element from Docx4j: paragraphs, rows, etc.</p>
Custom deployments solutionhttp://www.solasistim.net//posts/custom_deployments_solution/2017-12-09T13:20:30Z2017-12-09T00:00:00Z
<p>Sometimes you may have a reason to deploy certain code. This normally involves
something like the following: you copy some files to a certain server somewhere,
and perhaps restart a server. This is all well known territory, but due to the
vagaries of SSH, automating it can often be a pain. There are existing tools
for this, Fabric and Capistrano, that are fairly well known but -- it seems
to me -- underused. Anyway, they're certainly far from standard, and
particularly with regard to Fabric (which I like and use on a near-daily
basis, I should point out) they can be tricky to get installed and configured
in their own right.</p>
<p>I devised this simple, perhaps even simplistic, plan to handle deployments.</p>
<ol>
<li><p>Create a UNIX user that will be used for deployments. For this article
we'll refer to this user as <code>dply</code>, although the name is immaterial. This
user must exist on the hosts that are the <em>target</em> of the deployments.</p></li>
<li><p>Distribute SSH keys to the hosts that need to initiate deployments. This
will often be a worker node in a CI system but people may also manually
initiate deployments.</p></li>
<li><p>Each deployment target receives an appropriate sudoers file that allows them
to execute one command (and one only): the deployment processor, with the
<code>NOPASSWD</code> specifier.</p></li>
<li><p>The deployment user <code>dply</code> can write to a mode <code>700</code> directory that is used
to receive deployment artifacts. Artifacts are written by a simple scp
process to this directory, <code>/home/dply</code> or whatever you like.</p></li>
<li><p>The deployment processor script, which is distributed identically to all the
nodes and lives in <code>/usr/local/bin</code>, knows about all existing deployments,
which are hardcoded with plaintext aliases like <code>main-site</code>, <code>backend</code>, etc,
and knows to look for the artifacts in <code>/home/dply</code> or whatever.</p></li>
<li><p>Nodes simply scp up the deployment archive, ssh to the relevant server and
invoke <code>sudo /usr/local/bin/deployment-processor backend</code>. The processor then
looks for the files in a hardcoded location and does whatever's needed to
actually deploy them. Concretely in this case every handler is just a
function in Perl which can then do many tasks. The key is that it doesn't
get any input from the user, thus mitigating some security issues. It's easy
to do the various things you may need to do, untar an archive, perhaps
chmod some files, restart a service, etc.</p></li>
</ol>
<p>It's secure in some senses, but not in others. There's no access isolation
between nodes so any node can deploy any code. Once a CI worker node is assumed
penetrated, a malicious user can indeed wipe out a production site, but they
can't do damage to existing servers. (for whatever that's worth...)</p>
<p>It should be noted that no consensus exists around solutions in this space. It
has some virtues over Fabric and probably Capistrano to, by being markedly less
complicated, because it only relies on the presence of ssh and scp on the client
boxes, which are near-universal. If you wanted to formalize it you could
develop cross-platform deployment client binaries in Go or something similar,
but I haven't found this necessary. Anecdotally I've had many unpleasant
problems with fabric, although it remains a very useful piece of software.</p>
<p>I don't like to deploy with Git because I don't see Git as something that's
related to deployments, Git is related to source code history which is distinct
from something that I might consider a "release artifact". FWIW release
artifacts are also built using a separate processing step, which (for me) is
often just a "narrowing" of the file tree according to a set of rsync patterns
and tarring up this narrowed tree.</p>
<p>Heroku also have an approach that involves creating "slugs" and "releases" where
each release corresponds to a deployment, and "to create a release" is synonymous
with "to deploy". This is much more featureful than the above approach but it's
over-engineered for this case.</p>
<p>There's also WAR deployment which is interesting but specific to a rather small
area of Java development. If you're a Java-only shop, this can probably be nice.</p>
<p>Something that was also on my radar in my department is the Perl-based
<a href="https://www.rexify.org/">Rex</a>, which I never got the chance to investigate.</p>
SCons and Google Mockhttp://www.solasistim.net//posts/scons_and_google_mock/2017-12-04T20:00:00Z2017-11-30T00:00:00Z
<p>I'm doing a project in C++ at present and experiencing mixed feelings about it.
One of the worst things about using C++ is the necessity to come into contact
with CMake, which is the bane of my existence. When I used to work on ProjectM,
I used to wrestle with this system and ended up hating it. Anyway, now I'm
starting a fresh C++ project, I started using the less popular (but far more
ergonomic) SCons.</p>
<p>Anyway, like many C++ projects GoogleMock has a bias for CMake. So if you want
to use SCons to build instead, here is a tiny <code>SConstruct</code> that you can use.</p>
<pre><code>googletest_framework_root = "/home/amoe/vcs/googletest"
googletest_include_paths = [
googletest_framework_root + "/googletest",
googletest_framework_root + "/googletest/include",
googletest_framework_root + "/googlemock",
googletest_framework_root + "/googlemock/include"
]
gtest_all_path = googletest_framework_root + "/googletest/src/gtest-all.cc"
gmock_all_path = googletest_framework_root + "/googlemock/src/gmock-all.cc"
env = Environment(CPPPATH=googletest_include_paths)
env.Program(
target='main',
source=["main.cc", gtest_all_path, gmock_all_path],
LIBS=['pthread']
)
</code></pre>
<p>Where your <code>main.cc</code> is a regular test driver, as such:</p>
<pre><code>#include &lt;gmock/gmock.h&gt;
int main(int argc, char **argv) {
testing::InitGoogleMock(&amp;argc, argv);
return RUN_ALL_TESTS();
}
</code></pre>
<p>You'll need to find some answer to actually getting the source of the test
framework into the build filesystem -- Google Test doesn't build as a system
library. That could be git submodules, a download script, or just dumping the
repository inside yours.</p>
Sunday Lamb Aloohttp://www.solasistim.net//posts/sunday_lamb_aloo/2017-12-04T21:41:50Z2017-11-19T00:00:00Z
<p>This is based on Camilla Panjabi's recipe. The only variations were, not using
any cloves (that she mentioned in the recipe but not in the ingredients list --
a possible erratum?) and using pre-cooked lamb. I got the lamb from the
butcher, a large leg joint on the bone. I stewed the entire joint for an hour
and a half in a large pot, with some curry powder &amp; balti masala for flavouring,
which I presume didn't form a large part of the flavour of this dish itself, but
I thought that since I plan to reuse the stock I may as well infuse something
into it. The meat slid off the bone rather easily after that, with small pinker
patches inside after cutting.</p>
<p>This curry has the singular innovation of creating the cumin-flavoured potatoes
first: you saute a big handful of cumin seeds and shallow-fry whole small
potatoes to give them a crispy skin on the outside. It looks very attractive
when finished. The cumin clings to the outside of the potato. Then later you
submerge these in curry liquid and boil them for about 10 minutes. This gives
whole potatoes that are still firm to the palate. I also used large chunky sea
salt on these potatoes.</p>
<p>The rest of it is rather standard. I didn't use a curry base for this one
because I had ran out, so the onions are reduced from scratch. The first thing
I noticed is what a long time it takes to get the onions the correct colour. It
took nearly a whole hour. That's the real benefit of using the base, IMO, the
time differential; there's probably not any large flavour benefit from a curry
base, perhaps there's even a flavour deficit.</p>
<p>This one strangely has garam masala formed into a paste and added relatively
early in cooking, which is somewhat of a departure.</p>
<p>I look forward to using this meat &amp; potatoes pattern in the future; potatoes are
great cupboard stock because they're cheap and last for ages. When you can boost
the bulk and variation of a curry by this addition, everybody wins.</p>
<p><a href="http://www.solasistim.net//images/DSCN3967.JPG"><img src="http://www.solasistim.net//posts/sunday_lamb_aloo/200x200-DSCN3967.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3970.JPG"><img src="http://www.solasistim.net//posts/sunday_lamb_aloo/200x200-DSCN3970.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3972.JPG"><img src="http://www.solasistim.net//posts/sunday_lamb_aloo/200x200-DSCN3972.JPG" width="200" height="150" class="img" /></a></p>
centos 6 debian lxc hosthttp://www.solasistim.net//posts/centos_6_debian_lxc_host/2017-11-03T16:25:02Z2017-11-03T16:25:02Z
<p>When using CentOS 6 as a container under a Debian Sid host, you may face the
following problem.</p>
<pre><code>amoe@inktvis $ sudo lxc-create -n mycontainer -t centos
Host CPE ID from /etc/os-release:
This is not a CentOS or Redhat host and release is missing, defaulting to 6 use -R|--release to specify release
Checking cache download in /var/cache/lxc/centos/x86_64/6/rootfs ...
Downloading CentOS minimal ...
You have enabled checking of packages via GPG keys. This is a good thing.
However, you do not have any GPG public keys installed. You need to download
the keys for packages you wish to install and install them.
You can do that by running the command:
rpm --import public.gpg.key
Alternatively you can specify the url to the key you would like to use
for a repository in the 'gpgkey' option in a repository section and yum
will install it for you.
For more information contact your distribution or package provider.
Problem repository: base
/usr/share/lxc/templates/lxc-centos: line 405: 24156 Segmentation fault (core dumped) chroot $INSTALL_ROOT rpm --quiet -q yum 2&gt; /dev/null
Reinstalling packages ...
mkdir: cannot create directory ‘/var/cache/lxc/centos/x86_64/6/partial/etc/yum.repos.disabled’: File exists
mv: cannot stat '/var/cache/lxc/centos/x86_64/6/partial/etc/yum.repos.d/*.repo': No such file or directory
mknod: /var/cache/lxc/centos/x86_64/6/partial//var/cache/lxc/centos/x86_64/6/partial/dev/null: File exists
mknod: /var/cache/lxc/centos/x86_64/6/partial//var/cache/lxc/centos/x86_64/6/partial/dev/urandom: File exists
/usr/share/lxc/templates/lxc-centos: line 405: 24168 Segmentation fault (core dumped) chroot $INSTALL_ROOT $YUM0 install $PKG_LIST
Failed to download the rootfs, aborting.
Failed to download 'CentOS base'
failed to install CentOS
lxc-create: lxccontainer.c: create_run_template: 1427 container creation template for mycontainer failed
lxc-create: tools/lxc_create.c: main: 326 Error creating container mycontainer
</code></pre>
<p>This is due to <code>vsyscall</code> changes in recent kernels. To get this working, you
need to add <code>vsyscall=emulate</code> parameter to your kernel command line (to be
perfectly specific, the command line of the <em>host</em> because containers share a
kernel.) To do this you can modify <code>/etc/default/grub</code> and run <code>update-grub</code>.</p>
eduroam network manager connectionhttp://www.solasistim.net//posts/eduroam_network_manager_connection/2017-11-13T15:16:45Z2017-11-02T16:31:09Z
<p>Update 2017-11-13: Now you need to use
<code>ca-cert=/etc/ssl/certs/QuoVadis_Root_CA_2_G3.pem</code>. I suppose they changed the
certificate.</p>
<p>Here's a NetworkManager connection for Eduroam. This can live under
<code>/etc/NetworkManager/system-connections</code>. Fill in your MAC address, your
username and password, plus a unique UUID.</p>
<pre><code>[connection]
id=eduroam
uuid=3ef39a8f-3020-4cf3-8e07-382719a4e6f9
type=wifi
permissions=
[wifi]
mac-address=10:02:B5:ED:5D:EB
mac-address-blacklist=
mode=infrastructure
ssid=eduroam
[wifi-security]
auth-alg=open
key-mgmt=wpa-eap
[802-1x]
ca-cert=/etc/ssl/certs/AddTrust_External_Root.pem
eap=peap;
identity=db57@sussex.ac.uk
password=YOUR_PASSWORD_GOES_HERE
phase2-auth=mschapv2
[ipv4]
dns-search=
method=auto
[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=auto
</code></pre>
Gear Description & Review, Nov 2017http://www.solasistim.net//posts/gear_review_nov_2017/2019-03-13T23:43:21Z2017-10-29T00:00:00Z
<p>I have a lot of gear configured in a very specific setup that has been that way
for going on 2 years now. We can call this "configuration 2". Before
configuration 2, I used a Kaoss Pad KP3+, plus the MS-20 and the ESX1 sampler for
the setup, this I eventually found limiting because of the sequencing capabilities
of the sampler. You can arrange songs and record effect sequences, but triggering
chords on the synth is very limited, and getting seamless looping of chords for
sampled pads is nigh-on impossible using the ESX1.</p>
<p>The current setup is:</p>
<ul>
<li>Output: Behringer Xenyx ZB319 mixer</li>
<li>Path 1: Juno 106 → Eventide Space → mixer ch1</li>
<li>Path 2: MS-20 mini → Tonebone Hot British tube distortion → Moog Clusterflux → mixer ch2</li>
<li>Path 3: Nord Lead → Mojo Hand Colossus fuzz → Strymon Timeline → mixer ch3</li>
<li>Path 4: Korg ESX1 with replacement JJ ECC803 vacuum tubes (IIRC)</li>
</ul>
<p>All driven by Sequentix Cirklon. Space &amp; Timeline have MIDI inputs which can
be driven from the Cirklon as well. The ESX1 has an audio in port which enables
its use as an effects unit. It can receive a pre-mixed copy and apply effects
using the Audio In part on the step sequencer. (Although this can easily create
a feedback loop, leading to some fairly painful squealing.)</p>
<p>All gear is plugged into surge protected adapters and uses shielded cables for
connection, this is key, a lot of noise happened before I did this. It's worth
noting that there's still a lot of noise with this setup.</p>
<ol>
<li>The MS-20 is noisy, not insanely so, but noticably.</li>
<li>The Juno chorus is noisy. The rest is fine.</li>
<li>The Tonebone &amp; Colossus are insanely noisy, but you'd expect that given
that they are fuzz pedals.</li>
<li>The ESX1 is noisy (although less noisy than before the tube replacement.)</li>
</ol>
<p>The expensive pedals, Space, Timeline and Clusterflux are very clean-sounding.</p>
<h2>Path 1: 106 → Space</h2>
<p>The Space is a bit of an enigma. It seems to be capable of a huge variety of
sounds, but it's a pain in the arse to use. The BlackHole and Shimmer
algorithms sounds huge and are perfect for the type of music that I do.
ModEchoVerb is also extremely useful. The trouble is that I tend to use the 106
for pad sounds and in this case the 106's legendary chorus is somewhat disrupted
by the Space. It's difficult to get sounds that are "in the middle": either the
effects from the Space are barely noticable (although I don't use headphones),
or they are completely swamping the character of the input (for instance, when
using Shimmer and BlackHole, the 106's setting is nigh-on irrelevant).</p>
<p>I am speculating on moving the Space to MS-20.</p>
<h2>Path 2: MS-20 → Hot British → Clusterflux</h2>
<p>The original goal of this setup was because I know from experience that the
MS-20 excels at raw and cutting sounds, it's quite an aggressive sounding synth.
The Hot British combination is definitely great, to be honest it would probably
sound great through nearly any distortion, so I'm not totally sure that this is
taking advantage of the high-end quality of the HB. And because the MS-20 is a
monosynth I tend to use it less -- given that this is basically an amp in a box,
as I understand it, it would probably be more suited to a polysynth, where you
can imagine dialing in giant stoner chords. Regardless, if you feed an arpeggio
into the MS-20 and crank the resonance/cutoff you're in acid heaven. I might
replace it with some type of MIDI-controllable distortion. What I really want
for this is a really quiet digital distortion (or a noise gate, I guess).</p>
<p>The ClusterFlux -- I just don't get on with it, I'm not sure why, because I love
chorus and phasing. It would be very neat for some nice phased acid lead, but
I don't find that it gives that much use for what I do. It does give a fatter
chorus sounds that goes nicely with the MS-20's dual-VCO mode (one of the
most notorious "basic settings" I've heard). The phasing sounds really great
but I hardly ever use it. It's just too extreme a sound.</p>
<p>Incidentally I've found that the best route with the MS-20 is to take the time
initially to find a nice sound and design the rest of the piece around it. It
doesn't work very well the other way around. It's very difficult to find a
matching sound to an existing composition. You don't even need to use it
for melody, what can be nice is to just design little "hits" and articulations
on the beat.</p>
<h2>Path 3: Nord → Colossus → Timeline</h2>
<p>This works the best out of the paths. The Nord is designed for leads, hence the
name and is a treble-heavy synth. The Colossus transforms everything into
trancy acid and the Timeline -- well, I barely know how to program the Timeline,
I just use presets and they sound gorgeous. Some of the Timeline's presets are
a touch subtle, you need to have "space" in the sound to be able to detect them.
Just the initial preset, Melt Away, will blow you away when you hear it, it makes
everything sound gorgeous. All the other presets sound gorgeous and there's a
looping feature which I never use but it's nice to know it's there. Basically
everything that comes out of this path sounds good. The Colossus is pretty
tweakable as well -- perhaps a more restricted range of tones than the HB but
a much more practical range.</p>
<p>Future directions:
I'd like to integrate the KP3 back into the setup. The ESX1 I'd like to replace
with another, more basic sampler -- it's got too much functionality for what
I use it for now. Search for "super basic sampler" for some suggestions.
Reverb and delay on drums can sound nice, but I don't want to use the Eventide
for those moments. I think the reverb could sound good on the MS-20. The problem
with the MS-20 is that the sound can be a little bit sparse. I also want to
replace the MS-20 with a module. But the distorted MS-20 is a key sound so maybe
put a nova drive on there? And put the Hot British onto the 106. This can sharpen
up the tones of the 106 and stop it sounding so 80s. Plus we can get a low level
output signal from the 106 to stop the HB blowing up.</p>
<p>The Clusterflux -- well, the 106 doesn't need it. The MS-20 wouldn't need it
after you added the Space. All in all I may just sell it. Possibly if I saved
some space I could add another synth and put it through that -- that would be a
Waldorf FM synth -- but then we are starting to run out of connections for the
Cirklon. I'd say that the Clusterflux is focused on a sort of guitar-specific
sound, a kind of funky syncopated sound which we don't really use with the
MS-20. It probably sounds better on something that's being played "live", where
you're manually varying the note gate times to sync with the FX. I also want to
replace the 106 with a 106 module and the MS-20 with an MS-20 module. I rarely
play the MS-20 keyboard because the keyboard is too small. Full size keyboards
are quite underrated. But it is kind of useful having two keyboards when you
are working with someone else. Probably it's worth trying the Clusterflux on
both the 106 and the Lead to see what sounds better.</p>
Org-Mode with Capture and Xmonadhttp://www.solasistim.net//posts/org_mode_with_capture_and_xmonad/2019-03-13T23:44:38Z2017-10-29T00:00:00Z
<p>This is a useful configuration for a distraction-free note-taking system. Often
you want to quickly note something but don't want to break your flow too much.<br />
The result will be a single key-combination that will pop a frame, allow you to
write in a task, and have it be written to your default org-mode file.</p>
<p>Nearly all of the meat of this configuration comes from <a href="http://www.windley.com/archives/2010/12/capture_mode_and_emacs.shtml">Phil Windley's blog
post</a>. I
assume you already have experience with org-mode.</p>
<pre><code>;; org-capture code for popping a frame, from Phil Windley
;; http://www.windley.com/archives/2010/12/capture_mode_and_emacs.shtml
;; We configure a very simple default template
(setq org-capture-templates
'(("t" "Todo" entry (file "") ; use the default notes files
"* TODO %? %i %t")))
(defadvice org-capture-finalize
(after delete-capture-frame activate)
"Advise capture-finalize to close the frame"
(if (equal "capture" (frame-parameter nil 'name))
(delete-frame)))
(defadvice org-capture-destroy
(after delete-capture-frame activate)
"Advise capture-destroy to close the frame"
(if (equal "capture" (frame-parameter nil 'name))
(delete-frame)))
;; make the frame contain a single window. by default org-capture
;; splits the window.
(add-hook 'org-capture-mode-hook
'delete-other-windows)
(defun make-capture-frame ()
"Create a new frame and run org-capture."
(interactive)
(make-frame '((name . "capture")
(width . 120)
(height . 15)))
(select-frame-by-name "capture")
(setq word-wrap 1)
(setq truncate-lines nil)
;; Using the second argument to org-capture, we bypass interactive selection
;; and use the existing template defined above.
(org-capture nil "t"))
</code></pre>
<p>Now we add the binding to your (hopefully existing) call to the xmonad
<code>additionalKeysP</code> function, which uses emacs-style notation for the keybindings.</p>
<pre><code>popCaptureFrameCommand = "emacsclient -n -e '(make-capture-frame)'"
myConfig = desktopConfig
{
-- your existing config here
}
`additionalKeysP` [("M-o c", spawn popCaptureFrameCommand),
-- probably more existing bindings...
]
</code></pre>
<p>Now you can type M-o c, you'll be popped into a capture buffer, then C-c C-c
will save and file it and close the window. It'll appear as a top-level heading
in the file. You can change the template definition if you are more OCD-minded
but I find that this simplistic configuration works and stays out of my way.</p>
Srichacha Noodle Souphttp://www.solasistim.net//posts/srichacha_noodle_soup/2017-10-27T06:56:46Z2017-10-17T00:00:00Z
<p>This is now reaching the height of some ridiculousness, but this was made with
a base formed through the following process:</p>
<ol>
<li>Forming an oil-based marinade with the Srichacha rub mentioned in my earlier
post about the <em>kaeng pa</em>.</li>
<li>Taking the remnants of the marinade which weren't absorbed by the tofu.</li>
<li>Make a chicken stock from the carcass of a whole chicken that was leftover
from making Sri Owen's <em>ayam bakar</em>, about which more later.</li>
<li>When the <em>ayam bakar</em> is produced, it produces scorched chicken skin which
melts into a mixture of rendered fat and unidentified black pieces.</li>
<li>Combine the stock with the Srichacha marinade and the chicken skin.</li>
<li>Raise to a rolling boil for 10 minutes for safety.</li>
<li>Re-blend the entire mixture until smooth.</li>
<li>Let the mixture cool, the fat will rise to the surface.</li>
<li>Skim off all the fat, you should be able to just use a teaspoon. It will
form a kind of foamy mass. You won't get rid of all of it, though.</li>
<li>Pass the mixture through a fat separator. This will get rid of any clumps
and hopefully remove remaining fat.</li>
</ol>
<p>First cook the veggies by sauteeing them for around 5 minutes. It's good
to use shallots, though you don't need any spices. Don't overcook the
vegetables.</p>
<p>You'll now have a rather concentrated stock with a slightly bitter flavour. To
form it into something suitable for soup, you simply dilute with equal amount
of water (50% stock, 50% water). Then bring to the boil. Once boiling add
the noodles. Do the noodles until <em>al dente</em>. Don't add salt.</p>
<p>Wait for the stock &amp; noodle mix to cool, now add the veggies.</p>
<p>Post script: It's actually better not to mix the noodles with the soup until
you're ready to eat them, because they can absorb too much liquid and end up
mushy. You can save a separate container of the stock for boiling the noodles,
or you can just do them with water. But keep them separated if you want to
freeze the finished soup.</p>
Updating JAX-RS interop examplehttp://www.solasistim.net//posts/jaxrs_interop/2017-10-20T11:09:14Z2017-10-14T00:00:00Z
<p>In Emerick, Carper &amp; Grand's 2012 O'Reilly book, <em>Clojure Programming</em>, they
give an example of using Java interop to create JAX-RS services using a Grizzly
server. These examples are now outdated and don't work with recent versions
of Jersey.</p>
<p>Here's an updated version that is working correctly, at least for this tiny
example.</p>
<p><code>jaxrs_application.clj</code></p>
<pre><code>(ns cemerick-cp.jaxrs-application
(:gen-class :name cemerick_cp.MyApplication
:extends javax.ws.rs.core.Application)
(:import [java.util HashSet])
(:require [cemerick-cp.jaxrs-annotations]))
(defn- -getClasses [this]
(doto (HashSet.)
(.add cemerick_cp.jaxrs_annotations.GreetingResource)))
</code></pre>
<p><code>jaxrs_annotations.clj</code></p>
<pre><code>(ns cemerick-cp.jaxrs-annotations
(:import [javax.ws.rs Path PathParam Produces GET]))
(definterface Greeting
(greet [^String vistor-name]))
(deftype ^{Path "/greet/{visitorname}"} GreetingResource []
Greeting
(^{GET true Produces ["text/plain"]} greet ; annotated method
[this ^{PathParam "visitorname"} visitor-name] ; annotated method argument
(format "Hello, %s!" visitor-name)))
</code></pre>
<p><code>jaxrs_server.clj</code></p>
<pre><code>(ns cemerick-cp.jaxrs-server
(:import [org.glassfish.jersey.grizzly2.servlet GrizzlyWebContainerFactory]))
(def myserver (atom nil))
(def properties
{"javax.ws.rs.Application" "cemerick_cp.MyApplication"})
(defn start-server []
(reset! myserver (GrizzlyWebContainerFactory/create "http://localhost:8080/"
properties)))
</code></pre>
<p>This uses the following Leiningen coordinates to run.</p>
<pre><code>[org.glassfish.jersey.containers/jersey-container-grizzly2-servlet "2.26"]
[org.glassfish.jersey.inject/jersey-hk2 "2.26"]]
</code></pre>
<p>You probably also need to AOT some of these namespaces, I used <code>:aot :all</code> for
this example.</p>
GitLab CI -- configuration noteshttp://www.solasistim.net//posts/gitlab_ci/2017-10-13T15:41:28Z2017-10-13T00:00:00Z
<p>These are a few notes that I came across while trying to get GitLab CI working.</p>
<h2>Fulfil the system requirements</h2>
<p>There are some pretty insane system requirements for GitLab. You need at least
4GB of memory, which is not always so easy to come by in a VPS environment.
Even when you fulfil the system requirements, GitLab will run out of memory
and have to be "kicked" sometimes, in my experience. You could probably automate
this with some kind of systemd configuration, but I haven't tried that yet.</p>
<h2>Realize that things differ depending on your package</h2>
<p>gitlab hosts Debian packages themselves that are more up to date, but perhaps
less integrated with the rest of the system. For reasons, I was reluctant to use
the packages from upstream. Instead, I used some backported versions for Jessie
that were created by Pirate Praveen. You don't need to worry about this, because
gitlab has migrated to stretch, so you just need to choose: use the upstream
packages, or use the official Debian stable packages. You won't have problems,
unless you run across features that you need from the newer versions.</p>
<h2>Understand the GitLab CI environment</h2>
<p>There are several things to realize about GitLab CI. The environment can differ
a lot. The two primary environments are 'docker' and 'shell'. If you use
docker, you build up all of your infrastructure prerequisites from a docker base
image. Whereas if you use shell, your builds run under a regular shell
environment, as a special (non-root) user.</p>
<p>Personally, I found that although docker was easier to get started with, I got
benefits by moving to the shell executor, because having a preconfigured
environment eliminated some run-time from the integration test suite. These
problems could also have been resolved by creating my own docker base image, but
that seems to be putting the cart before the horse, in a way: I already have all
my infrastructure under configuration management, so why would I invest in a new
method of ensuring that the docker base image meets my needs when I can address
it with existing tools? Ther's also the problem that docker can't use background
processes as it doesn't have a real PID1.</p>
<h2>Understand Gitlab CI files</h2>
<p>They're in YAML format, which is well documented. There's an existing lint tool
for gitlab-ci.yml, which has stricter semantic rules than a simple YAML violation.
If you do just want to validate your YAML, you can use the snippet:</p>
<pre><code>validate_yaml() {
ruby -e "require 'yaml';puts YAML.load_file(ARGV[0])" "$1"
}
</code></pre>
<p>Assuming you have ruby.</p>
<p>You can use "tags" to separate groups of runners. If you are trying to move
from docker to shell executors or vice versa, you can set <code>tags</code> in a job to
ensure that that job doesn't get executed on the wrong runner.</p>
<p>The key to understanding the execution cycle of GitLab CI jobs is that any job
could essentially be executed on any runner. That means that you can't make
assumptions about what files will be available at any time -- because if a job
<code>j1</code> executed on host <code>mercury</code>, when job <code>j2</code> executes later on host <code>venus</code>
then the output files produced by the build tool won't be available in <code>venus</code>.</p>
<p>There are two ways to get around this.</p>
<p>The first is to declare that the job has build <strong>artifacts</strong>.</p>
<pre><code>variables:
OUTPUT_TAR_PATH: "mybuild.tar"
compile:
stage: compile
script:
- sh do_build.sh $OUTPUT_TAR_PATH
tags:
- shell
artifacts:
paths:
- $OUTPUT_TAR_PATH
</code></pre>
<p>The GitLab CI runtime will automatically make sure that <code>$OUTPUT_TAR_PATH</code> is
copied between any runner hosts that are used to execute jobs in this build.</p>
<p>Another related mechanism is the cache:</p>
<pre><code>cache:
untracked: true
key: "$CI_BUILD_REF_NAME"
paths:
- node_modules/
</code></pre>
<p>This is useful for JavaScript projects, otherwise you're going to end up
downloading &gt;1GB of npm modules at every phase of your project.</p>
<p>One last point: command under a <code>shell</code> list are parsed in a special way and line
breaks or special characters may not always work as you expect. So far, it was
mostly trial and error for me. Don't assume that you need to quote unless you
try it and it fails: AFAIK, the interpreter does its own mangling to the shell
lines.</p>
Kaeng Karihttp://www.solasistim.net//posts/kaeng_kari/2017-10-23T15:00:04Z2017-10-13T00:00:00Z
<p>This is pretty thrown together, it's mostly adapted from the Rasa Malaysia
recipe (despite being a Thai dish). I had Srichacha marinade left over plus some
yellow curry paste from an old housemate.</p>
<p>There's really nothing to it: stir fry courgette and red pepper, add curry
paste, use bamboo shoots and fish sauce, add coconut milk, add previously
deep-friend tofu. Now you've got something insanely yummy.</p>
<p><a href="http://www.solasistim.net//images/DSCN3900.JPG"><img src="http://www.solasistim.net//posts/kaeng_kari/200x200-DSCN3900.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3901.JPG"><img src="http://www.solasistim.net//posts/kaeng_kari/200x200-DSCN3901.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3902.JPG"><img src="http://www.solasistim.net//posts/kaeng_kari/200x200-DSCN3902.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3903.JPG"><img src="http://www.solasistim.net//posts/kaeng_kari/200x200-DSCN3903.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3904.JPG"><img src="http://www.solasistim.net//posts/kaeng_kari/200x200-DSCN3904.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3905.JPG"><img src="http://www.solasistim.net//posts/kaeng_kari/200x200-DSCN3905.JPG" width="200" height="150" class="img" /></a></p>
Ayam Bakar (Sri Owen)http://www.solasistim.net//posts/ayam_bakar_owen/2017-10-23T14:49:32Z2017-10-12T00:00:00Z
<p>This is Sri Owen's <em>ayam bakar</em> (grilled chicken) which is rather different from
many of the other Javanese style recipes that I have found online. This one
requires roasting of the full chicken first, then a later grilling. It also
includes the chicken breast where most recipes include the leg only, and omits
the <em>kecap manis</em>.</p>
<p>Start off by roasting the whole chicken. The inside and outside is vigorously
rubbed with a lemon, butter and salt mix.</p>
<p><a href="http://www.solasistim.net//images/DSCN3893.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3893.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3894.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3894.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3895.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3895.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3896.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3896.JPG" width="200" height="150" class="img" /></a></p>
<p>After this you need to joint the whole chicken, which is not shown as it's
rather gory. (You can email me and I'll send you the X-rated version.)</p>
<p>Creating a spicy marinade, marinating the jointed chicken and grilling at the
top of the oven until blackened. The most notable thing about the marinade is
that it uses <em>terasi</em>, shrimp paste, which you can get from Thai-focused
oriental shops in the UK. This stuff is extremely pungent.</p>
<p><a href="http://www.solasistim.net//images/DSCN3907.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3907.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3908.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3908.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3909.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3909.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3911.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3911.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3912.JPG"><img src="http://www.solasistim.net//posts/ayam_bakar_owen/200x200-DSCN3912.JPG" width="200" height="150" class="img" /></a></p>
<p>The jury's still out on the flavour of this one. Eat it with rice and cucumber
slices, of course. I hope to update this post soon to give you a better idea,
but there are a lot of different recipes for this widespread Indonesian dish. I
think you're going to want some <em>sambal</em> anyway.</p>
Pangek Ikan (Sri Owen)http://www.solasistim.net//posts/pangek_ikan/2018-04-19T20:44:53Z2017-10-12T00:00:00Z
<p>This was interesting but there are several problems: It's a big faff to get the
fish and such, and the flavour's not strong enough. So let's distill a paste
that I call 'pangek paste'. Apparently <em>pangek</em> refers to thickening the spice
mixture before adding the main ingredient; we do it for roughly 10 minutes here,
but I suppose the longer, the more <em>pangek</em>!</p>
<p>I speculate that where Indo food uses 'asam kandis', or the Garcinia fruit, Owen
is substituting tamarind, which is much easier to get in the UK (although still
quite niche).</p>
<p>Lime should be added to the fish to enhance the bland flavour of the trout.</p>
<p>I suppose this would be more of a 'Minang paste':</p>
<ul>
<li>6 candlenuts</li>
<li>1 onion</li>
<li>4 garlic cloves</li>
<li>4 red chillies</li>
<li>2 tsp chopped ginger</li>
<li>1 tsp chopped galangal</li>
<li>1 tsp ground turmeric</li>
<li>4 tbsp tamarind water</li>
<li>4 tbsp coconut milk</li>
</ul>
<p>You can store this in a jar for n days, certainly not indefinitely. How much
does this work for? Well, according to the recipe this suits a huge 800ml of
coconut milk + easily 1kg of main ingredient. I'd say this is wrong, because
the flavour was insufficient. Use 500g of main ingredient and 400g of coconut
mlk for this.</p>
<p>You can add lemongrass paste to this, then you no longer need the lemongrass
stalks.</p>
<p>It'd be interesting, although possibly somewhat wrong, to combine this with the
BIR base. You'd be adding a flavour of browned onions, but potentially not that
much. The other thing is that the flavour relies on being 'cooked down'. So
perhaps what you'd want to do is to use 800ml of coconut milk but actually cook
it down into a thick paste, although reducing the volume by half would seem like
quite an ask: then you could even add pre-cooked chicken, or finish in the oven
in a covered vessel.</p>
<p>Ah, the mighty Pangek Ikan. Since this initial post, I've had the chance to
actually make this sauce, which is rather an eye opener. It produces a lovely
coconut sauce which is really a SNACK food; it's not a meal, you don't eat it
with rice. Not only that, you don't need to use all of the 'juice' when you eat
it. Just leave the contents relatively whole, which is easier to do if you use
chicken (although the sauce works marginally better with fish, but keeping fish
whole is very difficult.) Then you can reuse the sauce for days and days with
every new 'main ingredient'. If you use this paste that I've provided here you
can get a huge amount of time from this.</p>
PostgreSQL-based integration testing with Clojurehttp://www.solasistim.net//posts/postgresql_integration_tests/2017-10-12T15:23:08Z2017-10-12T00:00:00Z
<p>The integration test vs unit test debate is a minefield. More particularly, see
J.B. Rainsberger's talk, "Integrated Tests Are A Scam". I don't want to dig in
to this particularly for this post, except to say that definitely if you are
working on a greenfield project, watch that talk first.</p>
<p>Now for those who for reasons have to or want to do integration tests, there are
several options. The fundamental problem is that databases are inherently
stateful. Therefore, you need some way of getting back to a clean state at the
beginning of each test.</p>
<p>We assume that the user is using some kind of database migration tool to manage
the database development. That is that they already have "up" and "down"
migrations. In this case, it's reasonable to solve the problem by doing the
following:</p>
<ul>
<li>Before each test:
<ol>
<li>Run the "down" migrations.</li>
<li>Run the "up" migrations.</li>
</ol>
</li>
</ul>
<p>This is relatively trivial to implement as a fixture, assuming that you are using
clojure.test.</p>
<p>The problem comes when this becomes intolerably slow, which can easily happen
once you get above 10s of migrations. Let me refer back to the Rainsberger talk
now, which is a must-watch. If you want to stick with integration testing,
you'll need some hack or workaround. There are several possibilities.</p>
<p>One possibility is using database transactional rollbacks. I found that this
was impractical when interacting with real code, because of the possibility of
transactions committing from within the system under test itself.</p>
<p>There are several guides to this method:</p>
<ul>
<li>http://www.lispcast.com/clojure-database-test-faster</li>
<li>http://bobnadler.com/articles/2015/03/04/database-tests-with-rollbacks-in-clojure.html</li>
<li>https://www.reddit.com/r/Clojure/comments/376wjn/using_database_transactions_in_tests/</li>
</ul>
<p>However I found this wouldn't work for when the code under test was using more
hairy database interactions.</p>
<p>Another method that you can use is the following:</p>
<ul>
<li>Create a template database <em>t</em> that already has all "up" migrations applied.</li>
<li>Assume an ephemeral database named <em>e</em>.</li>
<li>For each test:
<ol>
<li>Drop <em>e</em>.</li>
<li>Make sure that <em>t</em> has all up migrations applied (should be a no-op).</li>
<li>Create <em>e</em> from template database <em>t</em>.</li>
<li>Run test against <em>e</em>.</li>
</ol>
</li>
</ul>
<p>The concept of a "template database" is PostgreSQL-specific, hence the post
title. In my experience, this procedure will introduce about a second of
latency to each test in practice (that's a rough measurement based on my laptop).
In this way it definitely precludes running the whole suite at once (and please
refer back to the Rainsberger talk), but can make isolated feature development
much faster.</p>
<p>You can see the code below. Note that <code>some-reporter</code> is a Ragtime reporter,
which has the signature <code>[data-store op id]</code>, as below.</p>
<pre><code>(defn reporter [data-store op id]
; ignore data-store
(case op
:up (debugp "migrating" id)
:down (debugp "rolling back %s" id)
:else (break)))
</code></pre>
<p>This is the functionality, with <code>drop-and-create</code> being the top-level function
that the fixture should use.</p>
<pre><code>; If the migration already happened, these tests will be fast in any case.
(defn migrate [db-spec]
(tracep "using authentication details" db-spec)
(repl/migrate {:datastore (ragtime.jdbc/sql-database db-spec)
:migrations (ragtime.jdbc/load-resources "migrations")
:reporter some-reporter}))
;; returns a vector
(defn get-drop-commands [test-databases-conf]
(let [leaf-db-name (get-in test-databases-conf [:leaf :database])]
[(format "DROP DATABASE IF EXISTS %s" leaf-db-name)
(format "CREATE DATABASE %s WITH TEMPLATE %s OWNER %s"
leaf-db-name
(get-in test-databases-conf [:root :database])
(get-in test-databases-conf [:leaf :username]))]))
;; also returns a vector
(defn get-reassign-commands [test-databases-conf]
(tracep "about to reassign" true)
[(format "REASSIGN OWNED BY %s TO %s"
(get-in test-databases-conf [:root :username])
(get-in test-databases-conf [:leaf :username]))])
(defn drop-and-create [test-databases-conf]
(try
(jdbc/db-do-commands test-db/root-postgres-db false
(get-drop-commands test-databases-conf))
(catch Exception e
(doseq [e0 e]
(println (.getMessage e0)))))
(try
(jdbc/db-do-commands (merge test-db/leaf-postgres-db
(select-keys test-db/root-postgres-db [:user :password]))
false
(get-reassign-commands test-databases-conf))
(catch java.sql.SQLException e
(doseq [e0 e]
(errorf e "unable to reassign permissions to ephemeral test role")))))
</code></pre>
<p>The following function also exists to forcibly disconnect other connections,
because this functionality has the unfortunate property that it will fail
if any other connections are already open.</p>
<pre><code>;; Requires account used by the template database to have the
;; superuser privilege in postgres.
(defn disconnect-other-connections []
(try
(jdbc/db-do-commands test-db/root-postgres-db false
"SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = 'my_ephemeral_test'
AND pid &lt;&gt; pg_backend_pid()")
(catch Exception e
(doseq [e0 e]
(println (.getMessage e0))))))
</code></pre>
<p>All that remains is to wrap this up in a <code>clojure.test</code> fixture. You can use
the <code>disconnect-other-connections</code> functionality or you can choose not to. Bear
in mind that this will introduce a large amount of complexity into your test
environment. Now you have to manage several databases in the test environment,
as well as role name/password permissions for each one.</p>
Chicken Tikka Balti Masalahttp://www.solasistim.net//posts/chicken_tikka_balti_masala/2017-10-23T15:08:47Z2017-10-06T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3870.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3870.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3871.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3871.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3872.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3872.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3875.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3875.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3876.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3876.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3878.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3878.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3879.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3879.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3880.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3880.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3881.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3881.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3882.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3882.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3883.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3883.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3886.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3886.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3887.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3887.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3889.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3889.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3892.JPG"><img src="http://www.solasistim.net//posts/chicken_tikka_balti_masala/200x200-DSCN3892.JPG" width="200" height="150" class="img" /></a></p>
Clojure Log Configurationhttp://www.solasistim.net//posts/clojure_log_configuration/2018-02-14T22:44:29Z2017-09-28T00:00:00Z
<p>Log configuration, overall I prefer to use <code>clojure.tools.logging</code> rather than
Timbre. No particular reason, only that I feel more comfortable with it.</p>
<p>Here's a really basic <code>logback.xml</code> file that you can put in your <code>resources</code>
subdirectory.</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;configuration&gt;
&lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
&lt;!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --&gt;
&lt;encoder&gt;
&lt;pattern&gt;%d{ISO8601,Europe/London} [%thread] %-5level %logger{36} - %msg%n&lt;/pattern&gt;
&lt;/encoder&gt;
&lt;/appender&gt;
&lt;!-- Show debug logs that originate from our namespace --&gt;
&lt;property name="level" value="DEBUG"/&gt;
&lt;logger name="net.solasistim" level="${level}"/&gt;
&lt;root level="INFO"&gt;
&lt;appender-ref ref="STDOUT"/&gt;
&lt;/root&gt;
&lt;/configuration&gt;
</code></pre>
<p>Appropriate dependency vectors for this are as follows.</p>
<pre><code>[org.clojure/tools.logging "0.4.0"]
[ch.qos.logback/logback-classic "1.1.3"]
</code></pre>
<p>This is generally sensible, make sure that you change <code>websocket-clj</code> to the
root of your code. Also note that only INFO or higher messages are shown from
any other code. While debugging, it's not unreasonable to change this level to
<code>&lt;root level="DEBUG"&gt;</code> or <code>&lt;root level="TRACE"&gt;</code>. Or if a library logs too much
at INFO, you can add explit exclusions for the package that issues it, as such:</p>
<pre><code>&lt;logger name="org.eclipse.jetty.server" level="WARN"/&gt;
&lt;logger name="org.eclipse.jetty.util.log" level="WARN"/&gt;
</code></pre>
<p>The log pattern will produce lines like this, when using <code>debugf</code>:</p>
<pre><code>2017-09-28 15:52:48,457 [nREPL-worker-0] DEBUG websocket-clj.util - foo: 42
</code></pre>
<p><code>[%thread]</code> may be redundant for you, but it can be very useful when you have
asynchronous actions occurring.</p>
<p>You also need these lines in <code>project.clj</code>.</p>
<pre><code>[org.clojure/tools.logging "0.4.0"]
[ch.qos.logback/logback-classic "1.1.3"]
</code></pre>
<p>Personally I always use logging in every project, because non-log methods of
output can sometimes suffer from strange race conditions that are generally
avoided by using a logging framework. Don't spend hours debugging a problem
that turns out to be an illusory artifact of the output function.</p>
<p><code>%logger{36}</code> means that the logger will abbreviate package names after 36
characters. This can be useful, or you can just use <code>%logger</code> to disable
abbreviation.</p>
<p>Note that in the <code>pattern</code> element, we don't use a <code>\n</code> to give a newline. I
don't know why, but in every environment I use this in, log statements
automatically receive newline termination. I'm still not entirely clear on why
this happens, because it doesn't happen in a pure Java project with the same
<code>logback.xml</code> file (although that's using Slf4j). More investigation is needed.</p>
Clojure Idioms: strict-gethttp://www.solasistim.net//posts/strict_get/2019-04-12T09:21:34Z2017-09-28T00:00:00Z
<p>Sometimes you want the benefits of a dynamically typed language with some of the
benefits of a statically typed one.</p>
<p>Many languages these days encourage the user to use dictionaries or maps as a
kind of loosely typed record.</p>
<p>These languages often differ on how they treat the situation where you look up
a nonexistent key in the map.</p>
<p>That's because they have to conflate two different purposes.</p>
<ol>
<li><p>The traditional data-structurish use of maps, where the keys are usually
calculated, and a nonexistent one could conceivably be valid. The classical
example is a word count program. If a program looks for an unknown key in the
word count map, it's reasonable to just return <code>null</code>, because the result will
clearly be identical to <code>nil</code>.</p></li>
<li><p>The record-like use of maps, where if you look up <code>:aghe</code> in a map with
has keys <code>:age</code>, it's obvious a programming error has occurred.</p></li>
</ol>
<p>This distinction is roughly related to the (rather outdated now)
<code>Exception</code>/<code>RuntimeException</code> distinction in Java. Although the checked exception
feature was such a horrible pain that many now derive from RuntimeException, the
previous notion was that you avoided catching RuntimeException. The
<code>RuntimeException</code> should kill your program, or at least the handling thread.</p>
<p>In the case of example 2, it's clear that <code>(get mymap :aghe)</code> can kill the
program. However, in Clojure that expression will return <code>nil</code>. At some other
time your code will get a wrong result. By that time, however, the stack trace
may be useless.</p>
<p><code>strict-get</code> will cause your code to break when you access a nonexistent field.
This function is also known as <code>grab</code> in the <code>tupelo</code> library.</p>
<pre><code>(defn grab [key_ map_]
(let [val_ (get-in map_ [key_] ::not-found)]
(if (= val_ ::not-found)
(do
(debugp "input was" map_)
(throw (IllegalArgumentException.
(format "unable to grab key '%s' from map" key_))))
val_)))
(defn strict-get [map_ key_]
(grab key_ map_))
(defn strict-get-in [map_ path]
(have! sequential? path)
(reduce strict-get map_ path))
</code></pre>
<p><code>debugp</code> is a personal debugging macro, feel free to replace it as you wish.
Also, <code>have!</code> is an assertion from the <code>truss</code> library.</p>
<p>Personally, I'd love for this to become a Clojure idiom, I find it incredibly
useful, especially when first writing a new piece of functionality. It's
important to remember, however, that you <em>always</em> need to think about your
intention when you do a map lookup. For me, <code>strict-get</code> is a good default, but
I still use <code>get</code>, <code>:foo</code>, and all the others in all their various permutations.</p>
<p>I wrote this macro that's a simplistic version of map destructuring, but using
<code>strict-get</code> instead:</p>
<pre><code>(defn generate-strict-get [keyword input-sym]
`(strict-get ~input-sym ~keyword))
(defn generate-let-pair [binding-sym keyword input-sym]
`[~binding-sym ~(generate-strict-get keyword input-sym)])
(defn generate-bindings-vector [bindings input-sym]
(-&gt;&gt; (map (fn &lt;span class="createlink"&gt;k v&lt;/span&gt;
(generate-let-pair k v input-sym)) bindings)
(mapcat identity)
vec))
;; (strict-ds-bind {foo :bar
;; baz :quux} input
;; expr)
(defmacro strict-ds-bind [bindings input &amp; expr]
`(let ~(generate-bindings-vector bindings input)
~@expr))
</code></pre>
<p>When you use this form, it's obvious what's happening, and the reader knows that
there's a chance that the continuation may throw at this stage. If you want to
get this to indent the body properly, you can use this snippet in your <code>.emacs</code>.</p>
<pre><code>(define-clojure-indent
(strict-ds-bind 2))
</code></pre>
<p>Thanks to this <a href="https://stackoverflow.com/questions/24443985/get-replacement-that-throws-exception-on-not-found">Stackoverflow
question</a>
for the main inspiration. Apparently <code>prismatic/plumbing</code> also calls this
<code>safe-get</code>.</p>
<p>I find this so useful that I find myself writing it in JavaScript as well, YMMV
though. Python is the only mainstream language that works like this by default,
as far as I know. (Update 2019: C++'s <code>at</code> method from <code>std::map</code> also works
like this.)</p>
Clojure Stripe integrations, circa 2017http://www.solasistim.net//posts/clojure_stripe_integrations_circa_2017/2017-09-26T19:44:57Z2017-09-26T00:00:00Z
<p>I'll do a quick round up of various libraries and implementation options here.
This is based on an evaluation from early 2017, but these things aren't changing
so much at present.</p>
<p>There are 4 wrapper libraries available on Github for interacting with the
Stripe API.</p>
<ul>
<li>clj-stripe</li>
<li>elephant</li>
<li>prachetasp/stripe-clojure (not evaluated here)</li>
<li>racehub/stripe-clj (not evaluated here)</li>
</ul>
<h2>clj-stripe</h2>
<p>clj-stripe is a library that's done in a slightly different style from many. It
features heavy use of multimethods, and wrapping Stripe operations in record
types. The operations are rather verbose. To do an operation you first
"create" an object representing the operation and then <code>execute</code> it. It has the
benefit of not relying on ambient state. In the end I didn't use this library,
because it was too cumbersome.</p>
<p>I ran into some problems with old TLS versions when running on Java 7. However,
hopefully, Java 7 is a distant memory by the time you read this.</p>
<h2>elephant</h2>
<p>Elephant is a supposedly "modern" library. In practice I found that it was
somewhat clumsy to work with. It uses global state, in the form of the
<code>set-api-key!</code> function. It delegates to the Java wrapper library, which then
wraps the REST API.</p>
<p>There are some weird things in working with the Elephant API. When you submit
calls to the API, you often use string keys in the map you are passing:</p>
<pre><code>(ecr/create {"source" token "email" email})
</code></pre>
<p>This is a little abnormal for a Clojure API and seems to be an indication of an
abstraction leak. (Actually Clojure is full of abstraction leaks, so it
wouldn't be alone, but I found this one rather distracting.)</p>
<p>But, when you get results from the Elephant APIs, the keys are automatically
"keywordized".</p>
<p>There are also some cases where you seem to need to do unnecessary calls in order
to perform certain actions, as in this example:</p>
<pre><code>(defn change-payment-source! [stripe-customer-id token]
(ecr/update (ecr/retrieve stripe-customer-id) {"source" token}))
</code></pre>
<p>Notice that we do two REST operations here. There's surely little point in this
network traffic (not that it matters so much in this case). I don't know the
REST API well enough to comment on if it's possible to do this in a single
network request, but I can't see why it wouldn't be possible? However, Elephant
requires a specially-tagged map that was returned from a previous Elephant call
as the first argument -- it can't take a plain ID.</p>
<p>The representation of result maps is also slightly confusing in itself -- the
maps seem to contain a fully "mapped" keywordized version of the data, as well as
a full string copy of the actual response? This is a tad awkward when trying
to read the data at the REPL.</p>
<h2>Things that it wasn't possible to do with these</h2>
<ul>
<li>Neither library has wrapper code for Stripe events, which you are pretty much
guaranteed to need if you need to handle subscriptions.</li>
<li>There's no way to get the total count of GET-collection operations.</li>
<li>There's no way to manually trigger payment of an invoice. (<code>Invoice.pay()</code>
in the Java API.) This can be useful when testing.</li>
<li>There's no automatic paging of results, which can be a touch tricky to
implement: for instance it's easy to just block until everything is returned and
process everything in a loop. But how long are you willing to block for? Or
would you prefer to pass some kind of callback or async action and process
results a page at a time? The possibilities are endless.</li>
</ul>
<h2>Lessons learned</h2>
<p>Elephant is quite easy to use for basic use cases. I can't remember where I
read this before, but some previous author said not to use wrappers when
interacting with external REST APIs in Clojure, and I almost tend to agree.
Overall, the time I spent dealing with the various quirks and imbrications of
these libraries probably outweighed the time it would take to create my own
wrapper. And don't forget that you have even more options: as well as the two
libraries <em>not</em> surveyed here, you can also use the Java API through
introspection, or go directly to REST. If I did it again, I think I'd go
directly to REST. And so probably end up creating <a href="https://imgs.xkcd.com/comics/standards.png">library number
5</a>...</p>
Full-Duplex Two Way Websocket Serverhttp://www.solasistim.net//posts/websocket_server/2017-09-27T18:39:32Z2017-09-19T00:00:00Z
<p>Having debated several ways of doing a soft-realtime app it seems that websockets
are indeed the path of least resistance.</p>
<p>Python provides the admirably ergonomic "websockets" module. This looks
superficially extremely simple, but getting this working was an insane
nightmare.</p>
<pre><code>#!/usr/bin/env python3
import asyncio
import websockets
import datetime
import random
import logging
async def consumer_handler(websocket):
while True:
logging.info("consumer looping")
await asyncio.sleep(5.0)
async def producer_handler(websocket):
while True:
logging.info("producer looping")
await websocket.send("foo")
await asyncio.sleep(1.0)
async def handler(websocket, path):
consumer_task = asyncio.ensure_future(consumer_handler(websocket))
producer_task = asyncio.ensure_future(producer_handler(websocket))
done, pending = await asyncio.wait(
[consumer_task, producer_task],
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
logging.basicConfig(level=logging.INFO)
print("Starting server")
start_server = websockets.serve(handler, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
</code></pre>
<p>Now we will try doing it in Clojure, using <code>aleph</code> and <code>manifold</code>.</p>
<p>A behaviourally-appropriate implementation would probably have to begin two
futures, each performing a <code>d/loop</code>, as above. Alternatively it may be possible
to fold one of them into the main thread.</p>
<pre><code>(defn long-function []
(Thread/sleep 1000))
(defn simple-async-ws-handler [req]
(let [s (-&gt; req http/websocket-connection deref)]
(d/loop []
(d/chain (stream/take! s)
(fn [_]
(d/future (long-function)))
(fn [_] (stream/put! s "foo"))
(fn [_] (d/recur))))))
(http/start-server simple-async-ws-handler {:port 8765})
</code></pre>
<p>I was able to extend this to the following example that is "full duplex", like
the Python example:</p>
<pre><code>(defn consume-stream [s]
(d/loop []
(-&gt; (stream/take! s)
(d/chain (fn [item]
(println "consumed")
(if item
(d/recur)
(println "end of stream"))))
(d/catch Exception #(println "did not work: " %)))))
(defn produce-to-stream [s]
(d/loop []
(-&gt; (long-function)
(d/chain (fn [_] (stream/put! s "foo"))
(fn [result]
(if result
(d/recur)
(println "stream end"))))
(d/catch Exception #(println "did not work: " %)))))
(defn duplex-handler [req]
(let [s (-&gt; req http/websocket-connection deref)]
(d/zip
(consume-stream s)
(produce-to-stream s))))
</code></pre>
<p>This is actually extremely neat, because it doesn't involve any explicit use of
futures, which now begin to feel rather "manual". I'm not totally sure that this
is actually doing what I want, though, but it seems a reasonable bet.</p>
<p>The exception handling is just necessary to avoid exceptions being shunted off
to nowhere when they happen asynchronously. Actually, manifold is good enough
to put them to <code>clojure.tools.logging</code>, which means that they end up in my
<code>*nrepl-server*</code> buffer.</p>
<p>The JavaScript client code for this is simple</p>
<pre><code>document.addEventListener("DOMContentLoaded", e =&gt; {
var exampleSocket = new WebSocket("ws://localhost:8765/");
console.log(exampleSocket);
exampleSocket.onopen = e =&gt; {
exampleSocket.send("Here's some text that the server is urgently awaiting!");
};
exampleSocket.onmessage = e =&gt; {
console.log("Received data: %o", e.data);
};
});
</code></pre>
<p>You can also connect to it from elisp, using the <code>websocket.el</code> library.</p>
<pre><code>(defun wstest-ws ()
(websocket-open
"ws://127.0.0.1:8765"
:on-message (lambda (_websocket frame)
(message "Message call happened")
(message (format "%S" (websocket-frame-text frame))))
:on-open (lambda (_websocket)
(message "Websocket opened")
(websocket-send-text _websocket "Hello, world")))
'created-socket)
</code></pre>
Abouthttp://www.solasistim.net//posts/about/2017-09-18T09:17:53Z2017-09-18T09:17:53Z
<p>A note on measurements:</p>
<p>I use UK terminology in all cases. This is as follows:</p>
<p>Tomato puree is a thick concentrated paste made from reduced tomatoes that is normally sold in a tube or a jar.</p>
Philly Cheesesteakhttp://www.solasistim.net//posts/philly_cheesesteak/2017-09-18T10:36:19Z2017-09-14T10:14:26Z
<p>Recently I've become infatuated with American food, or at least the idea of it.
One of these is something as simple as the legendary Philadelphia Cheesesteak,
something that's pretty much unknown to most British people. To me, this sounds
glorious: it combines two of the world's greatest things, cheese and steak!</p>
<p>So I came across the great channel of
<a href="http://phillyjaycooking.com/">PhillyboyJay</a> who provides a nice video detailing
how to cook an authentic cheesesteak. And it's sure authentic as far as I can
tell, having never visited Philadelphia! I can only recommend his video, I
keep rewatching it to drool at the result.</p>
<p>It's remarkably simple to make: onion, rib-eye steak, and cheese and that's
basically it, plus Jay's seasonings: Worcestershire sauce, garlic powder, salt
and pepper. The challenging part is to fit the result into some existing form
of bread container that we can access in the UK. I remember at some stage you
were able to buy sub rolls -- what the Americans refer to as "hoagie rolls" --
in the UK, but I haven't seen them for a while, and they were always nearly
half the size of the US ones anyway.</p>
<p>For this I compromised by just using two hot dog rolls, which are readily
available. Honestly it's not great, because the ability to make a single
giant sandwich is really one of the attractions of this recipe, but we have to
make do with what we have, or as every political commentator keeps saying these
days, "we are where we are".</p>
<p>Melting the cheese inside the pan isn't really practical with these rolls sadly,
because they're too small to efficiently contain a liquidy result. Instead
we have to melt the cheese in the microwave. (boo!)</p>
<p>The result is delicious, though -- how could it not be? -- and feels nicely
indulgent, the springy-textured steak pieces having a hint, strangely, of the
squeaky texture of halloumi.</p>
<p>One thing that's easy to forget with this recipe is to use pepper!</p>
<p><a href="http://www.solasistim.net//images/DSCN3836.JPG"><img src="http://www.solasistim.net//posts/philly_cheesesteak/200x200-DSCN3836.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3837.JPG"><img src="http://www.solasistim.net//posts/philly_cheesesteak/200x200-DSCN3837.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3838.JPG"><img src="http://www.solasistim.net//posts/philly_cheesesteak/200x200-DSCN3838.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3839.JPG"><img src="http://www.solasistim.net//posts/philly_cheesesteak/200x200-DSCN3839.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3840.JPG"><img src="http://www.solasistim.net//posts/philly_cheesesteak/200x200-DSCN3840.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3841.JPG"><img src="http://www.solasistim.net//posts/philly_cheesesteak/200x200-DSCN3841.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3842.JPG"><img src="http://www.solasistim.net//posts/philly_cheesesteak/200x200-DSCN3842.JPG" width="200" height="150" class="img" /></a></p>
Welcomehttp://www.solasistim.net//posts/welcome/2017-09-18T10:38:46Z2017-09-13T08:42:15Z
<p>Welcome to Solasistim version 3. This is a successor to the previous
Boson-based web page.</p>
<pre><code>(display "Hello, world!")
</code></pre>
Srichacha Kaeng Pahttp://www.solasistim.net//posts/srichacha_kaeng_pa/2017-09-18T10:36:19Z2017-08-31T00:00:00Z
<p>Continuing with the quest for frugality and optimality in kitchen use, only
now deprived from all forms of paid employ. I create another marinade using
JB's Srichacha Seasoning / Rub, some trace of which is now removed from the
internet at large. You can find a <a href="https://baconsalt.3dcartstores.com/JDs-Sriracha-Rub-All-Purpose-Seasoning_p_188.html">trace of it</a>
still present as of 2017.</p>
<p>The <em>kaeng pa</em> is one of my favourites from my local Thai restaurant.</p>
<p>I just freestyled on this marinade, cutting out the crusted-up seasoning, adding
a ton of white wine vinegar and half-decent oil and blending it up in a jug
blender. The marinade changed colour a lot as the blender chewed through the
clumped together seasoning.</p>
<p>We prepare two large blocks of tofu by pressing, freezing then marinating, then
deep-frying the contents until black.</p>
<p><a href="http://www.solasistim.net//images/DSCN3708.JPG"><img src="http://www.solasistim.net//posts/srichacha_kaeng_pa/200x200-DSCN3708.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3710.JPG"><img src="http://www.solasistim.net//posts/srichacha_kaeng_pa/200x200-DSCN3710.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3711.JPG"><img src="http://www.solasistim.net//posts/srichacha_kaeng_pa/200x200-DSCN3711.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3712.JPG"><img src="http://www.solasistim.net//posts/srichacha_kaeng_pa/200x200-DSCN3712.JPG" width="200" height="150" class="img" /></a></p>
<p>Then the recipe itself is quite a normal stir fry with a Thai red curry paste.</p>
<p><a href="http://www.solasistim.net//images/DSCN3713.JPG"><img src="http://www.solasistim.net//posts/srichacha_kaeng_pa/200x200-DSCN3713.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3715.JPG"><img src="http://www.solasistim.net//posts/srichacha_kaeng_pa/200x200-DSCN3715.JPG" width="200" height="150" class="img" /></a></p>
<p>It looks gorgeous in the pan. It's also fiery as all hell, with a deliciously
full flavour. I found that it's actually the marinade that's doing this spice,
which is a little counterintuitive, given that it had a really not insubstantial
amount of curry paste inside it.</p>
<p><a href="http://www.solasistim.net//images/DSCN3822.JPG"><img src="http://www.solasistim.net//posts/srichacha_kaeng_pa/200x200-DSCN3822.JPG" width="200" height="150" class="img" /></a></p>
<p>This met with round approval from vegetarian housemates, although it's somewhat
cheating as it contains a fair amount of fish sauce (<em>nam pla</em>)</p>
Malaidar Aloohttp://www.solasistim.net//posts/malaidhar_aloo/2017-10-12T15:05:14Z2017-08-10T00:00:00Z
<p>I made this curry quite a while back now, in a wish to adapt a few more of
Camilla Panjabi's recipes in a BIRish style. "Malaidar" seems to translate
roughly as "creamy" -- so this would be "creamy potatoes", kind of like an
Indian style <em>gratin</em>?</p>
<p>This is a weird tasting dish. I have to say, the potatoes that I used to make
it were well past their best. Really I simply wanted to get rid of them. You
can see the state of them from the pictures. This curry uses a shocking amount
of ginger, and some spices that aren't seen that often in Indian cooking. Mace
and mint, anyone?</p>
<p>I messed up this recipe in two main ways. Drastically overdid the pepper, as you
can see in the picture. Honestly, don't know what I was thinking. And adding
a ludicrous amount of green chilli. There's so much chilli that without
mitigating it somehow, it's nearly inedible. Although the end result looks
relatively OK, the flavour has an unconscionable bitterness.</p>
<p>I eventually discovered that this can be mitigated quite well with Patak's
aubergine pickle, this pickle is incredibly useful for adding a sweetness to curry
without losing the authenticity by adding sugar. At this point it becomes
a pretty decent curry, although the chilli intensity is still detectable
underneath.</p>
<p>Honestly, based on my attempt I can't recommend this rather strange dish, but it
might be worth a second go if you happen to have some cream and potatoes going.</p>
<p><a href="http://www.solasistim.net//images/DSCN3683.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3683.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3687.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3687.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3690.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3690.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3698.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3698.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3700.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3700.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3701.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3701.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3702.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3702.jpg" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3703.jpg"><img src="http://www.solasistim.net//posts/malaidhar_aloo/200x200-DSCN3703.jpg" width="200" height="150" class="img" /></a></p>
BBQ Balti Chickenhttp://www.solasistim.net//posts/bbq_balti_chicken/2017-09-18T09:35:01Z2017-07-19T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3651.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3651.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3655.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3655.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3658.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3658.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3661.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3661.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3663.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3663.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3664.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3664.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3667.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3667.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3670.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3670.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3671.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3671.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3676.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3676.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3678.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3678.JPG" width="200" height="150" class="img" /></a></p>
<p>This is a bit of a spur of the moment thing, having to get rid of a large amount of JB's Bacon Rub I put together some pork ribs based on a recipe I found online, then having a huge amount of marinade left over I thought I may as well try to combine it with a balti masala that I had remaining. The result is a dry and thick curry that looks and smells very much the real deal. The flavour is fantastic to eat with naan. Really this one -- which for reference used the Toombs balti masala, but following the Chapman method -- reminded me of a really notable curry that I had once at a buffet meal at a university canteen, many years ago. For a long time I've been searching for that same scent, or at least wondering about it, not because it was the most amazing curry but because they had such a vast amount of it.</p>
<p>The BBQ marinade was done separately, the chicken done in the oven at high heat in the tandoori style. Then we mixed the chicken pieces into the curry very near to the end of cooking, to minimize the amount of flavour transference away from the pieces. I think that the fact that this ended up so characteristic is down to several factors: a lack of tomatoes (which are not used by Chapman), the relatively small amount of water (just half a mug), and a large amount of veg oil used during cooking, so much that I had to skim it off.</p>
<p>The marinade used BBQ sauce, bacon rub, porter ale, Mexican chile sauce, soy sauce, Worcestershire sauce, dark rum, mustard. Honestly the resulting flavour works very well, giving a strongly balti outer layer that's counterpointed by a warm sweetness once you bite into the chicken. It's quite a lot of effort for just a single dish, though, so I might save this one for a summer barbecue season. Once you make the marinade you can probably get a solid 3kg of meat out of it, provided you use it cannily.</p>
<p>This is one that, to me, clearly needs to be eaten with naan rather than rice. (This is something that I'm still just starting to develop intuitions about.)</p>
<p><a href="http://www.solasistim.net//images/DSCN3680.JPG"><img src="http://www.solasistim.net//posts/bbq_balti_chicken/200x200-DSCN3680.JPG" width="200" height="150" class="img" /></a></p>
Sabzi Kormahttp://www.solasistim.net//posts/sabzi_korma/2017-09-18T09:35:01Z2017-07-18T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3636.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3636.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3637.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3637.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3639.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3639.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3643.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3643.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3642.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3642.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3645.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3645.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3647.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3647.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3649.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3649.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3650.JPG"><img src="http://www.solasistim.net//posts/sabzi_korma/200x200-DSCN3650.JPG" width="200" height="150" class="img" /></a></p>
<p>This is based on an adapted recipe from Julie Sahni. It's a traditional vegetarian korma that works on a basis that she seems to adopt throughout the book -- cooking vegetables in remarkably small amounts of liquid. I increased the amount of liquid provided, because it just seemed too little to even cook the vegetables, though if I'd been more daring I might have tried it. Is it possible to cook vegetables purely in a covered pan with little heat? I suppose that would be steaming, really. Anyway, you can imagine that these vegetables are partially steamed, in this sense at least. Really the korma-ish aspect is the blanched almonds, which should really be blended to a powdery paste. I didn't have the wherewithal to approach this for the time, so I just smashed them up with a strong chef's knife, using the patented grip of the chef's knife.</p>
<p>"That was good... Very good when eaten drunk. Probably also sober." -- Lisa T.</p>
<p>Tasting notes: Good, mild, doesn't taste alarmingly special or really have the character of the korma, though. I'm not quite sure what to eat this with. (Probably good to examine Sahni.) Rice seems to rob it of some character, and it's almost better with less salt to enhance the mild effect.</p>
Vegetable Tikka Masalahttp://www.solasistim.net//posts/vegetable_tikka_masala/2017-09-18T09:35:01Z2017-07-02T00:00:00Z
<p>This curry is formed with oven-made marinated vegetables: cauliflower, courgette, and mushrooms. Rather non-traditional vegetables for Indian cuisine, but they marinate well. It was made for my vegetarian housemates as a snack on BBQ day, but I ended up with a lot of leftovers; I thought I'd put them into a classic tikka masala, using the Edwards sauce.</p>
<p><a href="http://www.solasistim.net//images/DSCN3589.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3589.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3608.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3608.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3609.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3609.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3610.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3610.JPG" width="200" height="150" class="img" /></a></p>
<p>Here you can see the tandoori marinade, with Turkish yoghurt. This was tandoori masala from a packet, unlike the previous lamb tikka. Unfortunately I wasn't able to get any shots of the finished <em>tikka</em> pieces, before they went into the sauce... Anyway, we simply mix it up with a fairly standard Edwards base and hot mix.</p>
<p><a href="http://www.solasistim.net//images/DSCN3611.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3611.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3612.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3612.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3613.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3613.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3614.JPG"><img src="http://www.solasistim.net//posts/vegetable_tikka_masala/200x200-DSCN3614.JPG" width="200" height="150" class="img" /></a></p>
<p>After adding some cream, we get the much-loved silken texture of the tikka masala.</p>
<p>This arrived near perfect. Perhaps not quite as perfect as if it had animal carcasses in it, and perhaps the vegetables can use slightly less time to sear in the oven -- possibly 10 minutes? But it definitely *tastes* like tikka masala. It strikes me that the warming feeling of this curry means it's rather better suited as a winter dish, for cold weather.</p>
Hybrid Tamil Lamb Shoulder Madrashttp://www.solasistim.net//posts/hybrid_tamil_lamb_shoulder_madras/2017-09-18T10:41:52Z2017-06-30T00:00:00Z
<p>Inspired by my recent trip to Sri Lanka, I decided to try for a more "authentic" flavour, as opposed to the classic BIR flavour, while keeping the convenience of the BIR base [in this case an Edwards base]. We don't pre-cook for this case: we use lamb shoulder and stew for a long time. This ends up as a rather wet curry, due to the addition of water. As a result it goes well with rice.</p>
<p>The end flavour is remarkably different to the traditional BIR style indulged here before: the lamb fat blends beautifully with the mellow coconut to produce a lovely counterbalance to the heat from the spices. It's a comforting dish that somehow reminds me of childhood.</p>
<p>Summary and pictures:</p>
<p><a href="http://www.solasistim.net//images/DSCN3582.JPG"><img src="http://www.solasistim.net//posts/hybrid_tamil_lamb_shoulder_madras/200x200-DSCN3582.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3583.JPG"><img src="http://www.solasistim.net//posts/hybrid_tamil_lamb_shoulder_madras/200x200-DSCN3583.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3584.JPG"><img src="http://www.solasistim.net//posts/hybrid_tamil_lamb_shoulder_madras/200x200-DSCN3584.JPG" width="200" height="150" class="img" /></a></p>
<p>First, construct the spice paste, smash it with a hand blender, and fry it up with the base sauce.</p>
<p><a href="http://www.solasistim.net//images/DSCN3585.JPG"><img src="http://www.solasistim.net//posts/hybrid_tamil_lamb_shoulder_madras/200x200-DSCN3585.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3587.JPG"><img src="http://www.solasistim.net//posts/hybrid_tamil_lamb_shoulder_madras/200x200-DSCN3587.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3588.JPG"><img src="http://www.solasistim.net//posts/hybrid_tamil_lamb_shoulder_madras/200x200-DSCN3588.JPG" width="200" height="150" class="img" /></a></p>
<p>Roughly dice the lamb shoulder. Add to the pot and stew, covered, for 60-90 minutes.</p>
<p>Full recipe:</p>
<p>This is a blend of a Dhillon recipe with a Panjabi recipe from Tamil Nadu. It serves 6 so is rather large. Bear in mind that the Dhillon base ends up far more 'wet' and watery than other bases can be. I essentially ceiling'd it at 400 for this recipe because that's a very large amount of curry base. A lot of water is used during the cooking so the result should still be very stewy.</p>
<p>As the classic garam masala contains cinnamon, cloves and black pepper, we allow the garam masala to do the 'hot' spicing for this version, simplifying the spice and coconut mix. We don't use the traditional Edwards or Dhillon spicings, as the aim is to create something different while preserving the basic BIR method. As the Edwards base is roughly twice as concentrated as the Dhillon base, we just use the equivalent for one 'portion' from Edwards and double it. The meat doesn't get pre-cooked in this case, it's stewed in the pot.</p>
<ul>
<li>5tbsp vegetable oil</li>
<li>400ml curry base (Edwards)</li>
<li>1 tsp salt</li>
<li>2tsp ginger paste</li>
<li>2tsp garlic paste</li>
<li>400ml coconut milk</li>
<li>60g creamed coconut</li>
<li>1lb stewing lamb (diced shoulder or leg)</li>
<li>3 fresh tomatoes, chopped</li>
<li>1 tsp garam masala</li>
<li>1 tsp tomato puree</li>
</ul>
<p>Extra spices:</p>
<ul>
<li>Pinch of turmeric</li>
<li>3tsp paprika</li>
<li>2tsp coriander powder</li>
<li>1tsp poppy seeds (optional)</li>
<li>1/2tsp fennel seeds</li>
<li>2 bay leaves</li>
<li>2 tsp chilli powder [Dhillon]</li>
</ul>
<p>Process creamed coconut in a blender with ginger and garlic pastes, plus the
extra spices, to form a paste, adding water if necessary.</p>
<p>Heat oil. Add half the curry base, stirring, heating it through. Reduce the heat
to a simmer. Add spice paste and tomato puree and fry for a few minutes. Add the
remaining base. Fry for 15 minutes, adding a further 3tbsp of water over this
period.</p>
<p>Add meat and saute for 5 minutes over a moderate heat. Add the tomatoes and
saute for a further 5 minutes. Add salt to taste.</p>
<p>Add 800ml of warm water and cook, covered, until the meat is done (about 50-60
minutes). Then add the coconut milk, stir in the garam masala, and simmer for a
few minutes before serving.</p>
Soto Ayamhttp://www.solasistim.net//posts/soto_ayam/2018-08-29T01:32:34Z2017-06-08T00:00:00Z
<p>Made with homemade chicken stock + breast from a whole chicken carcass. This is
unbelievably spicy, having been made with leftover <em>sambal</em> from the Char Kuay
Teow. The spice hits the back of your throat instantly: it's only eased by a
heavy dose of salt. The prawns lend a nice fishy tang. I had this with a big
dose of overly-cooked Thai jasmine rice, as you can see: this makes it a full-on
meal. The chicken pieces are cooked with the soup itself, and they turn out
nice, although it's hard to keep exact control of how "done" they get. Still,
the pure breast is a far cry from the authentic Indo <em>warung</em> experience, where
they'd undoubtedly just hack up the carcass with a meat cleaver and chuck a
random assortment of bones, gristle and meat into the pan. The flavour, though,
is very convincingly Indo; it's based on a recipe from Sri Owen.</p>
<p><a href="http://www.solasistim.net//images/DSCN3606.JPG"><img src="http://www.solasistim.net//posts/soto_ayam/200x200-DSCN3606.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3597.JPG"><img src="http://www.solasistim.net//posts/soto_ayam/200x200-DSCN3597.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3601.JPG"><img src="http://www.solasistim.net//posts/soto_ayam/200x200-DSCN3601.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3605.JPG"><img src="http://www.solasistim.net//posts/soto_ayam/200x200-DSCN3605.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3607.JPG"><img src="http://www.solasistim.net//posts/soto_ayam/200x200-DSCN3607.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3595.JPG"><img src="http://www.solasistim.net//posts/soto_ayam/200x200-DSCN3595.JPG" width="200" height="150" class="img" /></a></p>
<p>If you stopped smoking recently, or you have a cold, this one is going to really
clear your sinuses out!</p>
Glasgow South Indian Garlic Lamb Tikkahttp://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/2017-09-18T09:36:14Z2017-06-07T00:00:00Z
<p>This is fatty and garlicky, as you might expect from the name. Because I ran out of chilli powder, I was unable to make it to its correct degree of heat. I replaced chilli powder with Edwards' hot mix. The predominant flavour is really still given by the lamb tikka which is very strongly flavoured.</p>
<p>The flavour is rude, round and gamey, not very spicy but holding the characteristic long-lasting burn of the Naga. Garlicky, as you expect, and a touch sour, as if it's just on the turn, having somewhat "matured" in the fridge. The leg of lamb is tender and flavoursome, despite being frozen for several months.</p>
<p><a href="http://www.solasistim.net//images/DSCN3547.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3547.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3548.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3548.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3549.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3549.JPG" width="200" height="150" class="img" /></a></p>
<p>Adding a large amount of garlic butter to this scarily unhealthy curry, and stirring it into a consistency.</p>
<p><a href="http://www.solasistim.net//images/DSCN3552.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3552.JPG" width="200" height="150" class="img" /></a></p>
<p>Adding a big fat block of green chilli to the mix.</p>
<p><a href="http://www.solasistim.net//images/DSCN3554.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3554.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3555.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3555.JPG" width="200" height="150" class="img" /></a></p>
<p>Using the pre-cooked lamb tikka, add these to the mix.</p>
<p><a href="http://www.solasistim.net//images/DSCN3560.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3560.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3563.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3563.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3565.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3565.JPG" width="200" height="150" class="img" /></a></p>
<p>Add red masala paste and stir until it becomes homogenous.</p>
<p><a href="http://www.solasistim.net//images/DSCN3570.JPG"><img src="http://www.solasistim.net//posts/glasgow_south_indian_garlic_lamb_tikka/200x200-DSCN3570.JPG" width="200" height="150" class="img" /></a></p>
<p>The final result.</p>
Bombay Aloo w/Bunjarrahttp://www.solasistim.net//posts/bombay_aloo_w_bunjarra/2017-09-18T09:36:14Z2017-06-03T00:00:00Z
<p>This bombay aloo recipe is from Dhillon. It was done with the previous Jones bunjarra, see an earlier post, because I was out of onions. This is really a simple curried potato dish, with a hint of sourness from lemon and fresh tomatoes. The result is delicious but, more to the point, visually stunning.</p>
<p><a href="http://www.solasistim.net//images/DSCN3532.JPG"><img src="http://www.solasistim.net//posts/bombay_aloo_w_bunjarra/200x200-DSCN3532.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3533.JPG"><img src="http://www.solasistim.net//posts/bombay_aloo_w_bunjarra/200x200-DSCN3533.JPG" width="200" height="150" class="img" /></a></p>
<p>Boiling chopped potatoes in a salted turmeric infusion.</p>
<p><a href="http://www.solasistim.net//images/DSCN3538.JPG"><img src="http://www.solasistim.net//posts/bombay_aloo_w_bunjarra/200x200-DSCN3538.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3535.JPG"><img src="http://www.solasistim.net//posts/bombay_aloo_w_bunjarra/200x200-DSCN3535.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3539.JPG"><img src="http://www.solasistim.net//posts/bombay_aloo_w_bunjarra/200x200-DSCN3539.JPG" width="200" height="150" class="img" /></a></p>
<p>Activating the bunjarra, fry the onion and add the usual assortment of BIR ingredients. Notably we're using half a lemon and chopped tomato for a tangy sourness. We put the drained potatoes in at the end, we can easily do this in parallel.</p>
<p><a href="http://www.solasistim.net//images/DSCN3540.JPG"><img src="http://www.solasistim.net//posts/bombay_aloo_w_bunjarra/200x200-DSCN3540.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3541.JPG"><img src="http://www.solasistim.net//posts/bombay_aloo_w_bunjarra/200x200-DSCN3541.JPG" width="200" height="150" class="img" /></a></p>
<p>Cover with chopped coriander and serve immediately. But be careful, it's hot! :)</p>
Chicken Dopiazahttp://www.solasistim.net//posts/chicken_dopiaza/2017-09-18T09:40:04Z2017-06-01T00:00:00Z
<p>As we all know, the dopiaza means "double onions". So there must be onions cooked in two ways. In truth, in this recipe there are onions cooked in 3 ways! Onion from the curry base, sliced softened onion, and premade bunjarra, for which see my previous post. It also contains a small amount of tomato for a hint of sourness.</p>
<p>One thing that I left out of this recipe is kashmiri chilli powder, which was a pure oversight as I do actually have these chillies in stock. It was done with chicken on a whim: I was just zipping through Sainsbury's and couldn't be bothered to think about cooking anything fancy. But pre-cooking is always needed for BIR recipes.</p>
<p>The chicken was an interesting case with this one, actually: often I have the problem when braising chicken that chilled, or worse, frozen chicken will reduce the temperature of the stock, meaning that it's impossible to get any real idea of how long the chicken's been at the correct temperature. I often ended up overcooking it as a result. This time, I resolved to take more risks, and speculated that even though it took a good ten minutes to bring to the boil after being dunked, that time could still be factored into the cooking time. The results were fine.</p>
<p><a href="http://www.solasistim.net//images/DSCN3513.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3513.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3514.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3514.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3518.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3518.JPG" width="200" height="150" class="img" /></a></p>
<p>Pre frying the softened onion, and precooking the chicken, as you can tell I did rather badly with the quantities here and overflowed the pan frequently.</p>
<p><a href="http://www.solasistim.net//images/DSCN3515.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3515.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3517.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3517.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3519.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3519.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3520.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3520.JPG" width="200" height="150" class="img" /></a></p>
<p>A familiar method: Activating the precooked sauce, this still using the Edwards base, my last remaining bit of that sauce, which has lastest the better part of 6 months. Draining the precooked chicken, and adding to the pot.</p>
<p><a href="http://www.solasistim.net//images/DSCN3527.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3527.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3522.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3522.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3523.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3523.JPG" width="200" height="150" class="img" /></a></p>
<p>Adding the bunjarra and sliced onions: left, the finished curry, after the addition of coriander. The colours in this one look beautiful!</p>
<p><a href="http://www.solasistim.net//images/DSCN3531.JPG"><img src="http://www.solasistim.net//posts/chicken_dopiaza/200x200-DSCN3531.JPG" width="200" height="150" class="img" /></a></p>
<p>Served with rice on the side. The flavour of this one is strong and satisfying, but something was slightly off. I'm not quite sure but I get some rather raw spice flavours sometimes. Although, really my palate is not sophisticated enough to differentiate a raw, cooked, or overdone spicing. Regardless, this needs a lot of salt, otherwise the significant pungent flavours can unpleasantly overpower the others. Salt seems to help to keep the pungency in check.</p>
LJ Bunjarrahttp://www.solasistim.net//posts/lj_bunjarra/2017-09-18T09:40:04Z2017-05-31T00:00:00Z
<p>Bunjarra is the 'real' name for a spiced onion paste that's used as a prerequisite for the <em>dopiaza</em>. This heavily concentrated paste can also be plopped into other curries to give them that savoury tang. You'd use about a heaping dessert-spoon at a time; this'll give you about 4 portions worth. The recipe itself uses 6 onions.</p>
<p><a href="http://www.solasistim.net//images/DSCN3500.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3500.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3501.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3501.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3502.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3502.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3497.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3497.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3504.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3504.JPG" width="200" height="150" class="img" /></a></p>
<p>The ingredients for the bunjarra. Note that we deep fry the onions in a healthy amount of oil, until they begin to turn brown at the edges.</p>
<p><a href="http://www.solasistim.net//images/DSCN3503.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3503.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3508.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3508.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3507.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3507.JPG" width="200" height="150" class="img" /></a></p>
<p>Frying up the deep-fried onions with the spices, notably a tandoori masala mix that's also given by LJ and depicted earlier. Making this tandoori masala is itself rather an undertaking. I might consider buying it in the future. Rajah sell pre-made mixes that could be convenient.</p>
<p><a href="http://www.solasistim.net//images/DSCN3511.JPG"><img src="http://www.solasistim.net//posts/lj_bunjarra/200x200-DSCN3511.JPG" width="200" height="150" class="img" /></a></p>
<p>After blending with a hand blender we have this final product. It packs a lot of flavour into a small space.</p>
Char Kway Teow 2: Rasa Malaysia Editionhttp://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/2017-09-18T09:40:04Z2017-05-25T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3495.JPG"><img src="http://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/200x200-DSCN3495.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3483.JPG"><img src="http://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/200x200-DSCN3483.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3485.JPG"><img src="http://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/200x200-DSCN3485.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3487.JPG"><img src="http://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/200x200-DSCN3487.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3489.JPG"><img src="http://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/200x200-DSCN3489.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3493.JPG"><img src="http://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/200x200-DSCN3493.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3494.JPG"><img src="http://www.solasistim.net//posts/char_kway_teow_2-rasa_malaysia_edition/200x200-DSCN3494.JPG" width="200" height="150" class="img" /></a></p>
<p>This was the reboot of the first one a couple of weeks ago, because I wasn't quite satisfied with it. This version is better, including a terrifying sambal made with the huge amount of chillies you can see on the scales there. Actually, I wimped out and only about 1/3 of that amount was eventually actually put into this dish. The flavour of this one is far better, not sure why, perhaps because I had some light soy sauce this time, and I was able to prevent the noodles clumping by just soaking them for more time. Even with the cut down levels of chilli, this is *pungent*. I have to say, I love making it too, it's quick and easy and most importantly uses up eggs. ;)</p>
Glasgow Lamb Shoulder Tikkahttp://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/2017-09-18T09:40:04Z2017-05-24T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3457.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3457.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3460.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3460.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3462.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3462.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3465.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3465.JPG" width="200" height="150" class="img" /></a></p>
<p>The Wilkie marinade mixed with some pre-made Glasgow sauce. The marinade recipe provided by Wilkie is nowhere near large enough to fully cover the volume of meat here, which is 1kg. As such it's more of a tikka-ish marinade with heavy onion flavouring.</p>
<p><a href="http://www.solasistim.net//images/DSCN3466.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3466.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3468.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3468.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3470.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3470.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3476.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3476.JPG" width="200" height="150" class="img" /></a></p>
<p>Drain out the marinade as best we can after 1 day, but it clings to the meat. We do it for 90 minutes, which was probably slightly over, given the charred nature of the result. Turned the meat once. I think it could probably have done with about 1/2 that time, but I was only following the packet instructions for the joint, which called for (N*30)+30 mins at 180C. The smell of the cooking lamb tikka is absolutely mouthwatering, filling the entire house with a barely resistible scent.</p>
<p><a href="http://www.solasistim.net//images/DSCN3479.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3479.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3481.JPG"><img src="http://www.solasistim.net//posts/glasgow_lamb_shoulder_tikka/200x200-DSCN3481.JPG" width="200" height="150" class="img" /></a></p>
<p>Afterwards, I scraped out the remaining marinade, which was thick with lamb fat. I blended it into the remaining sauce, forming a tikka-ish curry base. I used this to make the Glasgow South Indian Lamb Tikka Masaladar (on which more soon). You can see the resulting dish here, which was also made with a naga chilli. The flavour is strong and gamey, and can easily flavour double the amount of rice. It goes well with some tarka dal on the side (made according to the Toombs recipe), which calms it down a bit.</p>
Tofu Char Kway Teowhttp://www.solasistim.net//posts/tofu_char_kway_teow/2017-09-18T09:40:04Z2017-05-12T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3444.JPG"><img src="http://www.solasistim.net//posts/tofu_char_kway_teow/200x200-DSCN3444.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3447.JPG"><img src="http://www.solasistim.net//posts/tofu_char_kway_teow/200x200-DSCN3447.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3448.JPG"><img src="http://www.solasistim.net//posts/tofu_char_kway_teow/200x200-DSCN3448.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3451.JPG"><img src="http://www.solasistim.net//posts/tofu_char_kway_teow/200x200-DSCN3451.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3454.JPG"><img src="http://www.solasistim.net//posts/tofu_char_kway_teow/200x200-DSCN3454.JPG" width="200" height="150" class="img" /></a></p>
<p>Nice rice noodle dish from the BBC Good Food cookbook. Done with a remaining five spice marinade, to add to the Chinese flavour. Deep fried the tofu as one whole block first. You can see the darkened edges of the newly cut pieces in the photo. Put in a bunch of shelled peanuts at the end. The rice noodles sadly showed a bit of a tendency to clump together, which I'm not entirely sure how to resolve. There's a really fun part in this recipe where you can pour beaten eggs into the centre of the wok and then 'fold' the noodles over the top: extremely satisfying.</p>
King Prawn Baltihttp://www.solasistim.net//posts/king_prawn_balti/2017-09-18T09:40:04Z2017-04-24T00:00:00Z
<p>This is another of Toombs' recipes, that was nonetheless made with the Edwards base. I need to get the flavour of Toombs' balti masala before the flavour of the spices wilts. This was made with some shamelessly packaged supermarket king prawns. The amount's doubled from Toombs' online recipe; this dish also uses some ghee, which I prepared earlier. It's always good to have a nice slab of ghee on hand.</p>
<p>The resulting dish was somewhat "non-BIR" tasting, having more of an East Asian feel to it. This may be only because of my personal associations, though; we never had seafood curries in my household as a child, nor did I ever order king prawn at the local takeaway, despite that exalted status of prawn dishes in the typical UK restaurant.</p>
<p>The flavour is sour, tomatoey and very saucy. Being aware of the tendency of Toombs' Balti recipes to verge on the excessively sour, I drastically cut down the amount of vinegar and lemon present in this dish. It's still plenty sour enough, even with halving those amounts. The peppers don't get very cooked in this recipe, so they remain crunchy, as do the prawns, which contributes to the stir-fry feel. I like Dhillon's method of a deep-fried scorched pepper, which I didn't apply here.</p>
<p><a href="http://www.solasistim.net//images/DSCN3402.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3402.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3404.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3404.JPG" width="200" height="150" class="img" /></a></p>
<p>Mixing up the balti masala into a vinegary paste.</p>
<p><a href="http://www.solasistim.net//images/DSCN3406.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3406.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3409.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3409.JPG" width="200" height="150" class="img" /></a></p>
<p>Some beautiful looking ghee, and funky red peppers.</p>
<p><a href="http://www.solasistim.net//images/DSCN3410.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3410.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3411.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3411.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3414.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3414.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3415.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3415.JPG" width="200" height="150" class="img" /></a></p>
<p>Proceeding from the concentrated base sauce to a spiced activated sauce.</p>
<p><a href="http://www.solasistim.net//images/DSCN3416.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3416.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3417.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3417.JPG" width="200" height="150" class="img" /></a></p>
<p>Adding in the prawns to be quickly cooked. I probably ended up cooking them for a bit too long.</p>
<p><a href="http://www.solasistim.net//images/DSCN3428.JPG"><img src="http://www.solasistim.net//posts/king_prawn_balti/200x200-DSCN3428.JPG" width="200" height="150" class="img" /></a></p>
<p>This is sometimes really nice but most of the time is just too sour. I get the strange idea that this could have been enhanced massively by adding paneer to the recipe, which would mitigate some of the sourness. Also, after reading Pat Chapman's "Balti Bible" (on which more soon), I get the idea that Toombs' instruction to 'activate' the balti masala using 125ml of vinegar is a large overexaggeration. Using Chapman's balti masala, he instructs to use just enough to form a sludge that can drip off the spoon.</p>
Ad-hoc Quorn Rogan Joshhttp://www.solasistim.net//posts/ad_hoc_quorn_rogan_josh/2017-09-18T09:40:04Z2017-04-15T00:00:00Z
<p>This was done with Quorn fillets and remaining Edwards rogan sauce.</p>
<p><a href="http://www.solasistim.net//images/DSCN3442.JPG"><img src="http://www.solasistim.net//posts/ad_hoc_quorn_rogan_josh/200x200-DSCN3442.JPG" width="200" height="150" class="img" /></a></p>
<p>Ad-hoc, because it was done in a near-automatic fugue the eve before departing for a holiday. Here you can see the curry mixed with brown basmati and aubergine pickle.</p>
Glasgow Vindaloohttp://www.solasistim.net//posts/glasgow_vindaloo/2017-09-18T09:42:11Z2017-03-28T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3053.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3053.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3055.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3055.JPG" width="200" height="150" class="img" /></a></p>
<p>A bit of a different first part, as we fry the garlic and ginger paste without any activated base, we also include some fresh coriander and some previously frozen Green Chilli Paste, as was previously used for the balti.</p>
<p><a href="http://www.solasistim.net//images/DSCN3056.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3056.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3060.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3060.JPG" width="200" height="150" class="img" /></a></p>
<p>Then we add the pre-cooked Glasgow chicken directly from frozen.</p>
<p><a href="http://www.solasistim.net//images/DSCN3062.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3062.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3064.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3064.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3067.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3067.JPG" width="200" height="150" class="img" /></a></p>
<p>Adding red masala sauce, the Glasgow base, fresh tomatoes, and a big hunk of chilli powder.</p>
<p><a href="http://www.solasistim.net//images/DSCN3073.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3073.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3077.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3077.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3082.JPG"><img src="http://www.solasistim.net//posts/glasgow_vindaloo/200x200-DSCN3082.JPG" width="200" height="150" class="img" /></a></p>
<p>The finished article served with Patak's green chilli paste. The result is delicious. Plenty salty enough without the addition of salt, somehow. The chilli powder was added a little bit late for me: I think it may be wise to add the chilli powder earlier to ensure it doesn't stay raw in the output. This is an interesting vindaloo because it has a much deeper flavour, it's not pure pain, the chilli is offset by the slight creaminess from the base and red masala. I'd say it's the nicest BIR vindaloo I've had. However, you'd better be prepared for some fairly severe effects on your digestion, that I leave as an exercise to the reader. It makes a good pairing with the previous Glasgow curries [Balti and Bombay Rogan Josh] described herein, because you can reuse the Red Masala and Green Chilli Paste.</p>
Rempeyekhttp://www.solasistim.net//posts/rempeyek/2017-09-18T09:40:04Z2017-03-26T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3043.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3043.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3042.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3042.JPG" width="200" height="150" class="img" /></a></p>
<p>To begin with, you make a batter from rice flour, then create a blended mix featuring candlenuts, coriander and garlic. You mix these two together.</p>
<p><a href="http://www.solasistim.net//images/DSCN3044.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3044.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3046.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3046.JPG" width="200" height="150" class="img" /></a></p>
<p>Laboriously shell the peanuts, then stir them into the batter. You can form the batter into a biscuit-like shape.</p>
<p><a href="http://www.solasistim.net//images/DSCN3045.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3045.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3047.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3047.JPG" width="200" height="150" class="img" /></a></p>
<p>Fry the rempeyek batter in a huge pan with very hot groundnut oil. It should start to crisp up. The priority here it to get it flat, which will be difficult due to the texture of the dough. As you can see, mine didn't turn out very flat.</p>
<p><a href="http://www.solasistim.net//images/DSCN3048.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3048.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3051.JPG"><img src="http://www.solasistim.net//posts/rempeyek/200x200-DSCN3051.JPG" width="200" height="150" class="img" /></a></p>
<p>Not shown: After the initial fry to crisp up the boundaries of the rempeyek, you deep fry them in batches for a couple of minutes. The texture should be golden, a few shown here ended up too dark.</p>
<p>The end result is a savoury, crispy, nutty snack, with a hint of fragrant coriander. It's actually very filling. For me, it needed a very liberal amount of salt added to be successful as a snack food. I'd add a larger amount of salt into the batter, because salt won't stick too well to the outside after it's been cooked. Also you should eat it ASAP as the texture becomes worse quickly. This snack is great in that it's extremely cheap (once you have the rice flour and candlenuts, which you may need to acquire from a specialist), and very filling. I can only eat these at lunch time, though, because they are hard to digest for me.</p>
Edwards Lamb & New Potato Rogan Joshhttp://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/2017-09-18T09:40:04Z2017-03-20T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN3003.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3003.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3006.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3006.JPG" width="200" height="150" class="img" /></a></p>
<p>Preparing a new batch of the Edwards Hot Mixture.</p>
<p><a href="http://www.solasistim.net//images/DSCN3007.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3007.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3010.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3010.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3011.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3011.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3009.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3009.JPG" width="200" height="150" class="img" /></a></p>
<p>Constructing a blended Rogan Sauce. Note the addition of liberal amounts of tomato puree to darken the colour of the sauce.</p>
<p><a href="http://www.solasistim.net//images/DSCN3013.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3013.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3014.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3014.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3015.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3015.JPG" width="200" height="150" class="img" /></a></p>
<p>Browning garlic in copious amounts of oil and then frying up the previously blended sauce with it. Note that this oil/liquid mix is unstable, as you can see in the picture.</p>
<p><a href="http://www.solasistim.net//images/DSCN3019.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3019.JPG" width="200" height="150" class="img" /></a></p>
<p>The final rogan sauce in a takeaway container.</p>
<p><a href="http://www.solasistim.net//images/DSCN3016.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3016.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3017.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3017.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3018.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3018.JPG" width="200" height="150" class="img" /></a></p>
<p>Precooking lamb with "activated" base. The Edwards base is characteristically thick and green. "Activating" it is usually a two stage process: add half the desired amount, cook for several minutes, add the second half, then dilute with water. This is only a pre-cooking process so it's debatable how useful this is compared to simply boiling the lamb. The other notable thing is we used lamb steaks here as opposed to a joint of lamb. These seem to have much lower cooking time than regular leg o'lamb. I struggled to find an accurate cooking time but ended up cooking for an hour to be on the safe side, which is probably far too much.</p>
<p><a href="http://www.solasistim.net//images/DSCN3027.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3027.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3029.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3029.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3030.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3030.JPG" width="200" height="150" class="img" /></a></p>
<p>Combining cooked lamb with rogan sauce to get the final Rogan Josh liquid. Note that this is extremely oily, and there's a strong ambiguity in the recipe because Edwards doesn't state how much rogan sauce to use. Here I used about half a takeaway container, which is about 1/4 of the amount created by the rogan sauce recipe. But, the result was still far too much sauce for the meat, given that the rogan josh is supposed to be a dry curry. This recipe only wants 100g of meat. I'd say that you can easily use 500g of meat with about 250ml of rogan sauce.</p>
<p><a href="http://www.solasistim.net//images/DSCN3032.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3032.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3034.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3034.JPG" width="200" height="150" class="img" /></a></p>
<p>As you can see the result is extremely oily and overly saucy.</p>
<p><a href="http://www.solasistim.net//images/DSCN3036.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3036.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN3039.JPG"><img src="http://www.solasistim.net//posts/edwards_lamb_and_new_potato_rogan_josh/200x200-DSCN3039.JPG" width="200" height="150" class="img" /></a></p>
<p>So I add some new potatoes that I had around in order to soak up some of the excess.</p>
Toombs Saag Baltihttp://www.solasistim.net//posts/toombs_saag_balti/2017-09-18T09:40:04Z2017-02-25T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN2999.JPG"><img src="http://www.solasistim.net//posts/toombs_saag_balti/200x200-DSCN2999.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2998.JPG"><img src="http://www.solasistim.net//posts/toombs_saag_balti/200x200-DSCN2998.JPG" width="200" height="150" class="img" /></a></p>
<p>This was done by me and M using the previously made balti masala, mushrooms, spinach, and Quorn mince. The flavour is sour, like the other Toombs balti, and also extremely spicy. It strongly benefits from Patak's aubergine paste which lends a sweetish flavour to it. I'd also point out that this used a bit of cream. This involves making a spinach sauce blended with coriander and chillies. The rest is mostly a standard curry with the Toombs base, except I believe that this one was in fact made with the Edwards base. The original recipe only specified onions. I'd say that this isn't really much of a balti in the form that it ended up in: I didn't serve it in a balti dish, and it actually ends up very saucy, far from the dryish stir fry definition of a balti. But it certainly is quick to cook, barely having any ingredients that require cooking.</p>
Glasgow Bombay Rogan Joshhttp://www.solasistim.net//posts/glasgow_bombay_rogan_josh/2017-09-18T10:45:31Z2017-02-21T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN2983.JPG"><img src="http://www.solasistim.net//posts/glasgow_bombay_rogan_josh/200x200-DSCN2983.JPG" width="200" height="150" class="img" /></a></p>
<p>This is Alex Wilkie's Glasgow Rogan Josh. It has a distinctive flavour that is given by the addition of lightly-sauteed onion, in addition to the heavily fried onions that are used in the base. This, along with the coriander, gives it a zingy fresh flavour. This is given the Glasgow touch with the addition of a liberal amount of garlic butter -- the Arabic روغن, 'rogan', referring to ghee -- and a touch of cream. This one was done with chicken, although lamb is recommended for a traditional rogan josh.</p>
<p>As you can see the appearance is nearly identical to the previous Glasgow balti, but the flavour is very different, being less spicy, the citrussy sourness being tempered by the cream and butter. Fresh tomatoes, characteristic of the rogan josh, were also used. It's also more "saucy" and certainly more fatty than the balti, as you can see here.</p>
Glasgow Chicken Baltihttp://www.solasistim.net//posts/glasgow_chicken_balti/2017-09-18T09:40:04Z2017-02-16T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN2978.JPG"><img src="http://www.solasistim.net//posts/glasgow_chicken_balti/200x200-DSCN2978.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2979.JPG"><img src="http://www.solasistim.net//posts/glasgow_chicken_balti/200x200-DSCN2979.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2980.JPG"><img src="http://www.solasistim.net//posts/glasgow_chicken_balti/200x200-DSCN2980.JPG" width="200" height="150" class="img" /></a></p>
<p>This one was the first example of the Glasgow style that I tried. I wanted to compare it to a previous "vanilla" BIR style -- that of the Dhillon balti, previously mentioned here. Certainly they were very different, in look and taste, but how much of that is the truth of their distinctive curry-forms, and how much of it is a mere coincidence, I can not tell. This was extremely good: it's very spicy and pungent, due to the coriander and fresh onions. I deviated from Wilkie's recipe by not blending the base sauce beforehand, out of sheer forgetfulness. The Glasgow base doesn't differ from a standard BIR base very much. The only notable difference is the sheer volume of sauce created (the recipe uses 4kg of onions!), and the addition of coconut cream. Many of the recipes use Patak's mixed pickle. I likely would have toned this down a tad, because it tends to dominate the flavour.</p>
Quorn Balti & Cloake Naanhttp://www.solasistim.net//posts/quorn_balti_and_cloake_naan/2017-09-18T09:40:04Z2017-02-03T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN2973.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2973.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2955.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2955.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2956.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2956.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2957.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2957.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2958.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2958.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2965.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2965.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2967.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2967.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2968.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2968.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2970.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2970.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2971.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2971.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2972.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2972.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2974.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2974.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2975.JPG"><img src="http://www.solasistim.net//posts/quorn_balti_and_cloake_naan/200x200-DSCN2975.JPG" width="200" height="150" class="img" /></a></p>
<p>This was made with Quorn fillets, plus Dan Toombs' "balti masala", which is a spice mix with some rather funky ingredients, notably onion seeds, citric acid and dried mint leaf. I didn't actually use these three as I didn't have them handy. It's notable that to actually use this balti masala, you're supposed to mix it with vinegar. The Toombs balti is very sour, involving 3tbsp of cider vinegar and 1 lime. Myself and M found it too sour overall, but it was nicely complimented by an aubergine pickle. I adapted it with the green pepper from Dhillon, which is deep-fried in large pieces until scorched.</p>
<p>M cooked Felicity Cloake's naan recipe. Homemade naan recipes have a lot of variation, all being essentially attempts to find a workable substitute for a tandoor. I attempted one a while back that failed spectacularly. This one went slightly better, because M is much better at working with dough than I am. The dough that Cloake recommends is wet and sticky and very difficult to handle. We also left it to rise, but it didn't rise very much. The result is double-dry-fried much like I regularly do when making corn tortillas. The mix is so sticky that you have to be very careful when moving it between pans. The end result is nothing like a restaurant naan, being half the size, but the flavour and texture are great.</p>
Two Spice Marinadeshttp://www.solasistim.net//posts/two_spice_marinades/2017-09-18T09:40:04Z2017-01-18T00:00:00Z
<p><a href="http://www.solasistim.net//images/DSCN2949.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2949.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2941.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2941.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2942.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2942.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2943.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2943.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2944.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2944.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2947.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2947.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2932.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2932.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2948.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2948.JPG" width="200" height="150" class="img" /></a>
<a href="http://www.solasistim.net//images/DSCN2931.JPG"><img src="http://www.solasistim.net//posts/two_spice_marinades/200x200-DSCN2931.JPG" width="200" height="150" class="img" /></a></p>
<p>I had these compiled bottles of spice mixture lying around my kitchen, probably for literally years, left by some housemate. I had no use for them, I don't normally use stuff like this. One day they annoyed me so much I decided to try and use them. So I adapted some marinade recipes for boned chicken: piri-piri marinade for chicken thighs, and five spice marinade for chicken drumsticks. Marinated them for one day and then attempted several ways of cooking them. I have a combination oven and grill. The five spice drumsticks were done in the oven and mostly worked out nicely. They go well with the new potatoes and salad, as shown in the pic. The piri piri marinade didn't go so well. I forgot to pierce the chicken before, so the marinade was barely detectable. [I suppose the spice was probably already extremely weak, having languished for years in my kitchen.] But worse, I couldn't find any way to cook it through. I think my oven is generally not very powerful, because everything that I cook in it seems to end up underdone. The piri piri thighs, I grilled for a long time and yet they just didn't cook, I was highly squicked as I gingerly poked the rubbery grey flesh. I eventually got some advice from my sister, and pre-baked them for 30 minutes and finished them for 20 minutes on the grill. Even then they were far from crisp, although quite edible with chips. So I wouldn't call these a success (the final piri piri chicken aren't shown here).</p>