<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Hugo Baldwin</title><link href="https://www.hugobaldwin.com/" rel="alternate"/><link href="https://www.hugobaldwin.com/feeds/all.atom.xml" rel="self"/><id>https://www.hugobaldwin.com/</id><updated>2025-10-30T00:00:00+00:00</updated><subtitle>I'm a software developer who likes to keep things simple.</subtitle><entry><title>90sleep - a sleep cycle calculator</title><link href="https://www.hugobaldwin.com/90sleep/" rel="alternate"/><published>2025-10-30T00:00:00+00:00</published><updated>2025-10-30T00:00:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2025-10-30:/90sleep/</id><summary type="html">&lt;p&gt;I've launched a new project: &lt;a href="https://90sleep.com?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=90sleep_launch"&gt;90sleep.com&lt;/a&gt; - a sleep cycle calculator to help you figure out the best times to sleep and wake.&lt;/p&gt;
&lt;p&gt;I was inspired by an old sleep calculator that's no longer available.  The original app worked by calculating sleep cycles between now and your targeted wake time, helping you wake during the shallow phase of your sleep rather than in the middle of a deep sleep cycle. I rebuilt it as 90sleep.&lt;/p&gt;
&lt;p&gt;The science is straightforward: sleep &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've launched a new project: &lt;a href="https://90sleep.com?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=90sleep_launch"&gt;90sleep.com&lt;/a&gt; - a sleep cycle calculator to help you figure out the best times to sleep and wake.&lt;/p&gt;
&lt;p&gt;I was inspired by an old sleep calculator that's no longer available.  The original app worked by calculating sleep cycles between now and your targeted wake time, helping you wake during the shallow phase of your sleep rather than in the middle of a deep sleep cycle. I rebuilt it as 90sleep.&lt;/p&gt;
&lt;p&gt;The science is straightforward: sleep cycles are roughly 90 minutes each, and an assumption is made that it takes approximately ~14 minutes to fall asleep.  The calculator helps you find optimal bedtimes and wake times based on this rhythm.&lt;/p&gt;
&lt;p&gt;There are two modes: "I need to wake up at..." which calculates the closest shallow sleep phase before your desired wake-up time, and "I'm going to bed now" which I see as a sort of emergency mode when I've stayed up farrrrr to late yet still need to be semi-functional in a few hours.  This displays the optimal wake times &lt;em&gt;from now&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The site is a static site, generated using Python and Jinja2 templates.  It's a similar approach to what I use for this blog (Pelican), but I've written a much simpler custom generator for this project. There's no server-side logic needed - the entire site is generated at build time.&lt;/p&gt;
&lt;p&gt;For styling, I'm using Tailwind CSS with a custom colour palette (dark background with warm amber accents) loosely inspired by Dieter Rams.  The interactive calculator itself is vanilla JavaScript - no frameworks needed for something this straightforward.  I'm also self-hosting the Inter font family to keep things performant.&lt;/p&gt;
&lt;p&gt;The build process is handled by a Python script that renders the Jinja2 templates and then the Tailwind CSS CLI minifies the CSS.  The site barely needs it given that it's a single-page app, but it keeps it consisent with a bunch of other static sites I run and that makes working on it easier.&lt;/p&gt;
&lt;p&gt;The site is deployed to Netlify, which handles the build and hosting automatically whenever I push to GitHub. I've integrated Plausible Analytics for privacy-respecting visitor analytics (no cookies, no tracking pixels).&lt;/p&gt;
&lt;p&gt;90sleep works well, but I'm keen to improve the search ranking and have a few feature ideas in the pipeline. If you use it and have suggestions, feel free to reach out!&lt;/p&gt;</content><category term="misc"/><category term="programming"/><category term="development"/><category term="sleep"/><category term="calculator"/><category term="tailwind"/><category term="netlify"/></entry><entry><title>Introducing Remotely.nz</title><link href="https://www.hugobaldwin.com/remotely/" rel="alternate"/><published>2024-07-17T00:00:00+01:00</published><updated>2024-07-17T00:00:00+01:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2024-07-17:/remotely/</id><summary type="html">&lt;p&gt;I've launched a new project: &lt;a href="https://remotely.nz?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=remotely_launch"&gt;Remotely.nz&lt;/a&gt; - a job board for genuinely remote work in New Zealand.&lt;/p&gt;
&lt;p&gt;My story is straightforward.  I fell in love with New Plymouth and want to settle here.  It's a beautiful place - good waves, good coffee, stunning landscape - but let's face it, it's no tech hub.  My search for a truly remote job in New Zealand opened my eyes to how broken the current job market is for remote workers here.&lt;/p&gt;
&lt;p&gt;The frustrations are real &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've launched a new project: &lt;a href="https://remotely.nz?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=remotely_launch"&gt;Remotely.nz&lt;/a&gt; - a job board for genuinely remote work in New Zealand.&lt;/p&gt;
&lt;p&gt;My story is straightforward.  I fell in love with New Plymouth and want to settle here.  It's a beautiful place - good waves, good coffee, stunning landscape - but let's face it, it's no tech hub.  My search for a truly remote job in New Zealand opened my eyes to how broken the current job market is for remote workers here.&lt;/p&gt;
&lt;p&gt;The frustrations are real. You think you've found the dream job on Seek, only to discover it's "hybrid" - requiring you to warm a seat in Grafton, Ponsonby, or a Wellington CBD office three days a week. That's not remote.  Or you find a job listing that says "work from home" but it's usually just a synonym for hybrid.  There's also the problem that most international remote job boards are heavily skewed towards the US and EU markets.  RemoteOK has thousands of jobs, but very few are suitable for someone based in New Zealand.&lt;/p&gt;
&lt;p&gt;And then there's Seek itself.  It doesn't even have a remote category!&lt;/p&gt;
&lt;p&gt;Remotely.nz cuts through all that noise. It aggregates job listings from multiple sources and filters them to surface genuine remote opportunities that actually work for kiwis.  The site is built using Python with Jinja2 templates, deployed to Netlify, and automatically updated on a daily basis.&lt;/p&gt;
&lt;p&gt;We're just getting started and I'm keen to work with recruiters and companies interested in featuring their remote positions.  The vision is simple: New Zealand has amazing locations outside the big cities - whether you're looking for a place to raise a family, chase better waves, or just escape the concrete jungle.  Your dream job shouldn't dictate your postcode.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href="https://remotely.nz?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=remotely_launch"&gt;Remotely.nz&lt;/a&gt; and let me know what you think. If you're a recruiter or company with genuinely remote roles that suit New Zealand workers, I'd love to chat about getting your positions featured. Or if you've got feedback or ideas, drop me a message.&lt;/p&gt;
&lt;p&gt;You can also join the community on &lt;a href="https://t.me/remotelynz"&gt;Telegram&lt;/a&gt; or follow us on &lt;a href="https://x.com/remotelynz"&gt;X&lt;/a&gt; for updates and discussions about remote work in New Zealand.&lt;/p&gt;</content><category term="misc"/><category term="remote work"/><category term="jobs"/><category term="side project"/><category term="New Zealand"/></entry><entry><title>I've launched BuyVsRent.co.uk</title><link href="https://www.hugobaldwin.com/buyvsrent/" rel="alternate"/><published>2024-07-03T00:00:00+01:00</published><updated>2024-07-03T00:00:00+01:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2024-07-03:/buyvsrent/</id><summary type="html">&lt;p&gt;I've launched &lt;a href="https://buyvsrent.co.uk/?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=launch"&gt;BuyVsRent.co.uk&lt;/a&gt;, a simple calculator to help you figure out whether buying or renting makes more financial sense.&lt;/p&gt;
&lt;p&gt;The motivation behind this came from a pretty simple problem: there are a lot of property calculators out there, but most of them are either incomplete, misleading or don't give much flexibility in the investment comparisons.  When I was trying to work through the numbers myself, I realised I needed something that actually accounted for investment returns on the &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've launched &lt;a href="https://buyvsrent.co.uk/?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=launch"&gt;BuyVsRent.co.uk&lt;/a&gt;, a simple calculator to help you figure out whether buying or renting makes more financial sense.&lt;/p&gt;
&lt;p&gt;The motivation behind this came from a pretty simple problem: there are a lot of property calculators out there, but most of them are either incomplete, misleading or don't give much flexibility in the investment comparisons.  When I was trying to work through the numbers myself, I realised I needed something that actually accounted for investment returns on the money I'd save by renting.  It felt weird that most calculators just ignored this entirely - if you're not buying a house, you're presumably investing that down payment somewhere.&lt;/p&gt;
&lt;p&gt;So I built this calculator to do a proper year-by-year financial simulation.  You input your local market data - house prices, rental costs, mortgage rates, property taxes, that sort of thing - and it compares what your net worth would look like over the course of the mortgage term if you bought versus if you rented.  The clever bit is that it assumes any monthly difference between buying and renting costs gets invested, and it tracks those investment returns alongside your home equity.  It also includes real investment options: Bitcoin, Ethereum, stocks, bonds, and gold with live data where available.&lt;/p&gt;
&lt;p&gt;The calculator comes pre-configured with market defaults for the UK and New Zealand, pulling data from official sources like the UK Land Registry and NZ's MBIE.  But you can override every single input if you want to model your own scenario.  The results include an interactive chart showing wealth accumulation over time, so you can actually see when one option becomes financially superior to the other.&lt;/p&gt;
&lt;p&gt;On the technical side, it's a lightweight static site built with Python and Jinja2, vanilla JavaScript for the calculations and Tailwind CSS for the styling.  No backend required - everything runs in the browser and builds to a flat set of files that deploy to Netlify.&lt;/p&gt;
&lt;p&gt;If you're thinking about property decisions - whether you're in the UK, New Zealand, or anywhere else - check it out and plug in your own numbers. I'd genuinely love to hear if it helps clarify things, or if you spot anything that could be better.  You can reach me on &lt;a href="https://t.me/byzantime"&gt;Telegram&lt;/a&gt; or &lt;a href="https://x.com/rentalyieldcalc"&gt;X&lt;/a&gt;.&lt;/p&gt;</content><category term="misc"/><category term="project"/><category term="calculator"/><category term="property"/><category term="finance"/><category term="renting"/><category term="investing"/></entry><entry><title>Rental yield calculator</title><link href="https://www.hugobaldwin.com/rental-yield-calculator/" rel="alternate"/><published>2023-02-27T00:00:00+00:00</published><updated>2023-02-27T00:00:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2023-02-27:/rental-yield-calculator/</id><summary type="html">&lt;p&gt;I put a new project online: &lt;a href="https://rentalyieldcalculator.co.uk?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=launch"&gt;rentalyieldcalculator.co.uk&lt;/a&gt;.  It's a simple tool to help mortgage lenders, letting agents, landlords (or potential landlords!) calculate their annual rental yield.&lt;/p&gt;
&lt;p&gt;There are many yield calculators online already but this one is mine :D and it's conveniently located at a memorable UK domain name.&lt;/p&gt;
&lt;p&gt;This is the first single page application I've created.  It's also the first form I've written which doesn't POST to a server.  The auto-calculate functionality was implemented using the &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I put a new project online: &lt;a href="https://rentalyieldcalculator.co.uk?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=launch"&gt;rentalyieldcalculator.co.uk&lt;/a&gt;.  It's a simple tool to help mortgage lenders, letting agents, landlords (or potential landlords!) calculate their annual rental yield.&lt;/p&gt;
&lt;p&gt;There are many yield calculators online already but this one is mine :D and it's conveniently located at a memorable UK domain name.&lt;/p&gt;
&lt;p&gt;This is the first single page application I've created.  It's also the first form I've written which doesn't POST to a server.  The auto-calculate functionality was implemented using the magnificent &lt;a href="https://hyperscript.org/"&gt;Hyperscript&lt;/a&gt;.  It reminds me of Jinja in the best of ways.  I love the readability - people constantly forget that programming languages are created for &lt;em&gt;humans&lt;/em&gt; first, &lt;em&gt;then&lt;/em&gt; computers.  Despite being written only once, programs tend to be refactored many times over, so language readability counts!&lt;/p&gt;
&lt;p&gt;I originally intended to write it in React, but the thought flashed into my head "I bet this'd be simpler and easier using &lt;a href="https://intercoolerjs.org/"&gt;Intercooler&lt;/a&gt;!"  It turns out Intercooler has been superceded by &lt;a href="https://htmx.org/"&gt;HTMX&lt;/a&gt; and (as far as I can tell) those talented people went on to create Hyperscript.&lt;/p&gt;
&lt;p&gt;This is now also the fourth website I've published using &lt;a href="https://netlify.com/"&gt;Netlify&lt;/a&gt; - it's just that fast and easy to use!  The sites almost all work via different mechanisms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hugobaldwin.com and htbdevelopment.com are both Pelican static sites;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://torquaytide.co.uk"&gt;a page predicting the best day/time to go swimming in Torquay&lt;/a&gt; - it's a Python script which calls weather and tidal APIs at buildtime then generates an HTML file;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rentalyieldcalculator.co.uk?utm_source=hugobaldwin.com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=launch"&gt;rental yield calculator&lt;/a&gt; - as described above, it's more or less a single page JS app.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm currently working on improving the search ranking of the site and also have a few other features planned.&lt;/p&gt;</content><category term="misc"/><category term="programming"/><category term="development"/><category term="hyperscript"/><category term="netlify"/><category term="rental yield calculator"/></entry><entry><title>Weekly routine changes</title><link href="https://www.hugobaldwin.com/weekly-routine-changes/" rel="alternate"/><published>2022-09-02T00:00:00+01:00</published><updated>2022-09-02T00:00:00+01:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2022-09-02:/weekly-routine-changes/</id><summary type="html">&lt;p&gt;I recently made four weekly routine changes all at the same time.  This is rare for me - the last comparable change I made would be in June 2019 when I decided (and started) to lift consistently three days a week.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Three weeks ago I started swiming in the sea at 6pm on workout days;&lt;/li&gt;
&lt;li&gt;A week ago I moved my 3x weekly weightlifting from 7pm to 5:30am;&lt;/li&gt;
&lt;li&gt;I stopped drinking coffee (usually 2 cups over the course of a &amp;#9758;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;I recently made four weekly routine changes all at the same time.  This is rare for me - the last comparable change I made would be in June 2019 when I decided (and started) to lift consistently three days a week.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Three weeks ago I started swiming in the sea at 6pm on workout days;&lt;/li&gt;
&lt;li&gt;A week ago I moved my 3x weekly weightlifting from 7pm to 5:30am;&lt;/li&gt;
&lt;li&gt;I stopped drinking coffee (usually 2 cups over the course of a day);&lt;/li&gt;
&lt;li&gt;I started drinking genmai-cha green tea (3-4x 400ml per day).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've noticed the following benefits:&lt;/p&gt;
&lt;p&gt;Working out in the evening, my motivation was generally low, with a nagging thought of "I'm tired, after this I'm going to get a shower and go to bed".  Working out in the morning, I want to complete the workout, have breakfast and start the day!&lt;/p&gt;
&lt;p&gt;No coffee / greatly reduced caffeine intake has meant no more anxiety, jitter, irritability or heart palpitations.  Also, no more coffee sweat - yuck!  I had mild headaches for a few days after dropping coffee but no more.&lt;/p&gt;
&lt;p&gt;I bought a 400ml double-walled glass bottle from Amazon which I fill up before starting my workout, then drink and re-fill as needed from then onwards until I finish work at the of the day.  The bottle being glass was important for me, as it allows me to see how much tea is left and acts as a constant reminder to drink - I previously had a problem staying sufficiently hydrated during the day.&lt;/p&gt;
&lt;p&gt;I feel calmer, more energy, more focused than previously and appreciate the ongoing mental focus throughout the day rather than the spikes and slumps I'd get from coffee.&lt;/p&gt;
&lt;p&gt;Physical recovery time has improved, although I'm unsure if that's from moving workouts to the morning, Theanine from the green tea or the shock to the body from swimming in the sea.&lt;/p&gt;
&lt;p&gt;Finally, I used to rise at 8am or later and wanted to rise earlier for a long time now but failed to make it happen.  This combination of changes seems to have helped it stick.&lt;/p&gt;</content><category term="misc"/><category term="weightlifting"/><category term="swimming"/><category term="coffee"/><category term="green tea"/><category term="theanine"/></entry><entry><title>How to Refactor Code</title><link href="https://www.hugobaldwin.com/how-to-refactor-code/" rel="alternate"/><published>2021-01-21T00:00:00+00:00</published><updated>2021-01-21T00:00:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2021-01-21:/how-to-refactor-code/</id><summary type="html">&lt;p&gt;Seems obvious, right?  You just edit it until it works the way you want it to.  Not so fast.  Reckless code changes will turn your project into a &lt;a href="https://en.wikipedia.org/wiki/Big_ball_of_mud"&gt;big ball of mud&lt;/a&gt; before you know it.  Done in the right way you'll experience less frustration and fewer crises.&lt;/p&gt;
&lt;p&gt;While you may write code as a developer, your main task is not writing code: it's removing ambiguity and mitigating project risks.&lt;/p&gt;
&lt;p&gt;If you're working on a personal project or in a &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Seems obvious, right?  You just edit it until it works the way you want it to.  Not so fast.  Reckless code changes will turn your project into a &lt;a href="https://en.wikipedia.org/wiki/Big_ball_of_mud"&gt;big ball of mud&lt;/a&gt; before you know it.  Done in the right way you'll experience less frustration and fewer crises.&lt;/p&gt;
&lt;p&gt;While you may write code as a developer, your main task is not writing code: it's removing ambiguity and mitigating project risks.&lt;/p&gt;
&lt;p&gt;If you're working on a personal project or in a small startup this is not for you.  This is written for the developers working in medium to large teams on mature codebases.&lt;/p&gt;
&lt;h2&gt;Small commits, named well&lt;/h2&gt;
&lt;p&gt;Keep your commits small and related to a single piece of functionality.  This allows them to be easily reverted later if necessary.&lt;/p&gt;
&lt;p&gt;Give your commits a descriptive summary - one which will help another developer to find the commit which introduced a particular feature (or bug!).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s"&gt;&amp;quot;TAG-789&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;added some more code&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;addressed merge request comments&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;Split duplicated image analysis code out into its own function&amp;quot;
&amp;quot;Added GET param to QR code image URL to avoid cache&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Small merge requests&lt;/h2&gt;
&lt;p&gt;Where possible merge requests should be kept small.  The more lines of code touched in the MR, the more likely it is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;to make your colleagues reluctant to review your code (especially since they're often under no obligation to do so)&lt;/li&gt;
&lt;li&gt;for you to introduce bugs into the codebase (there's a direct relationship between line count and bug count)&lt;/li&gt;
&lt;li&gt;for your reviewers to get fatigued and miss things&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes you might be updating a library and you might have to create a huge merge request.  That's ok - it's an exception.  The rule is, keep your MRs small.&lt;/p&gt;
&lt;h2&gt;Incremental improvements&lt;/h2&gt;
&lt;p&gt;Suppose the team has identified the use of some particular pattern is, in fact, an anti-pattern.  Unless you've been given the greenlight by your tech lead or dev team manager, do not go and strip out every occurrence of that pattern!  Doing so introduces risk to the project.  Instead, first &lt;strong&gt;raise a ticket&lt;/strong&gt; to remove the offending pattern from the codebase.  Then during the course of normal (unrelated) development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if you're touching an offending pattern, re-factor it&lt;/li&gt;
&lt;li&gt;if you're touching a function containing an offending pattern, re-factor it&lt;/li&gt;
&lt;li&gt;if you're touching a file containing an offending pattern but it's unrelated to the code at hand, add a &lt;code&gt;# TODO: refactor in [ticket number]&lt;/code&gt; comment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With any luck there'll be few to no occurrences left by the time the ticket is made priority.&lt;/p&gt;
&lt;h2&gt;Never write code without a ticket&lt;/h2&gt;
&lt;p&gt;Writing code is easy.  Writing good tickets is hard.  However that ticket is there to protect you.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your colleague has a brilliant one-liner which makes the feature you're working on even better?  Tell them to raise a ticket.&lt;/li&gt;
&lt;li&gt;The tech lead asks you what it'll take to implement X or Y feature?  You reply with &lt;code&gt;2 * (your best estimate)&lt;/code&gt;.  Perhaps offer to raise a ticket for them if one doesn't exist.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Including code which falls outside the scope of your ticket is &lt;a href="https://en.wikipedia.org/wiki/Scope_creep"&gt;scope screep&lt;/a&gt;.  Every new line of code increases the probability of new bugs and therefore project risk.  Additional code requires additional testing which delays the completion of the feature.  Delays add pressure to everyone in the team.&lt;/p&gt;
&lt;p&gt;So play it safe: ensure the code you're working on is part of a ticket and within scope.&lt;/p&gt;</content><category term="misc"/><category term="software"/><category term="methodology"/></entry><entry><title>A Gherkin styleguide</title><link href="https://www.hugobaldwin.com/a-gherkin-styleguide/" rel="alternate"/><published>2021-01-20T00:00:00+00:00</published><updated>2021-01-20T00:00:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2021-01-20:/a-gherkin-styleguide/</id><summary type="html">&lt;p&gt;Few, if any websites make useful recommendations about how to phrase Gherkin steps, so allow me to make some suggestions in the hope they will make life simpler.&lt;/p&gt;
&lt;h2&gt;Given steps establish state&lt;/h2&gt;
&lt;p&gt;Given steps describe a current, present, extant state of the test environment (I use the term loosely) before some (to be tested) action is performed.&lt;/p&gt;
&lt;p&gt;The fact that your Given step definition likely &lt;em&gt;creates&lt;/em&gt; that state in code behind the scenes is irrelevant.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Given steps must be in &amp;#9758;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Few, if any websites make useful recommendations about how to phrase Gherkin steps, so allow me to make some suggestions in the hope they will make life simpler.&lt;/p&gt;
&lt;h2&gt;Given steps establish state&lt;/h2&gt;
&lt;p&gt;Given steps describe a current, present, extant state of the test environment (I use the term loosely) before some (to be tested) action is performed.&lt;/p&gt;
&lt;p&gt;The fact that your Given step definition likely &lt;em&gt;creates&lt;/em&gt; that state in code behind the scenes is irrelevant.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Given steps must be in past or present tense&lt;/li&gt;
&lt;li&gt;Given steps must not be in the future tense&lt;/li&gt;
&lt;li&gt;Given steps must not contain action.  That's the job of When.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;Given &lt;/span&gt;&lt;span class="nf"&gt;I populate the database&lt;/span&gt;
&lt;span class="k"&gt;Given &lt;/span&gt;&lt;span class="nf"&gt;I have logged in as a user&lt;/span&gt;
&lt;span class="k"&gt;Given &lt;/span&gt;&lt;span class="nf"&gt;the welcome banner has been displayed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;Given &lt;/span&gt;&lt;span class="nf"&gt;the database is populated&lt;/span&gt;
&lt;span class="k"&gt;Given &lt;/span&gt;&lt;span class="nf"&gt;I am logged in as a user&lt;/span&gt;
&lt;span class="k"&gt;Given &lt;/span&gt;&lt;span class="nf"&gt;the welcome banner is displayed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the latter of each of the two examples "Given the welcome banner has been displayed" is ambiguous - is it still being displayed, or was it simply displayed at some point in the past?  Use the second example "Given the welcome banner is displayed", because it's unambiguous.&lt;/p&gt;
&lt;h2&gt;When steps perform an &lt;em&gt;action&lt;/em&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;When steps should not discuss state.  That's the job of Given and Then.&lt;/li&gt;
&lt;li&gt;When steps should be in present tense&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;When &lt;/span&gt;&lt;span class="nf"&gt;I make a transaction of £&lt;/span&gt;&lt;span class="s"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;When &lt;/span&gt;&lt;span class="nf"&gt;I flush the db connection&lt;/span&gt;
&lt;span class="k"&gt;When &lt;/span&gt;&lt;span class="nf"&gt;I load https://google.com/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Then steps make assertions about state&lt;/h2&gt;
&lt;p&gt;Then steps &lt;em&gt;do not&lt;/em&gt; talk about what will, should or may be, but what &lt;em&gt;is&lt;/em&gt; (or is not).
It's hard to make meaningful assertions about human intention ("will"), let alone software written by humans.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Then steps must be in present tense&lt;/li&gt;
&lt;li&gt;Then steps must make an assertion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;Then &lt;/span&gt;&lt;span class="nf"&gt;I will be emailed a receipt&lt;/span&gt;
&lt;span class="k"&gt;Then &lt;/span&gt;&lt;span class="nf"&gt;I will see the user login page&lt;/span&gt;
&lt;span class="k"&gt;Then &lt;/span&gt;&lt;span class="nf"&gt;the search input field will be displayed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;Then &lt;/span&gt;&lt;span class="nf"&gt;my inbox contains an email receipt&lt;/span&gt;
&lt;span class="k"&gt;Then &lt;/span&gt;&lt;span class="nf"&gt;the user login page is displayed&lt;/span&gt;
&lt;span class="k"&gt;Then &lt;/span&gt;&lt;span class="nf"&gt;the search input field is displayed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Your inbox either contains the expected email receipt or it does not.  Yes/No, True/False.  It's easy to test or make assertions about.&lt;/p&gt;
&lt;p&gt;Last but not least, only Then step definitions may contain code assertions.  You might think that's obvious and uncontroversial, but I've seen Given and When step definitions containing them.&lt;/p&gt;
&lt;h1&gt;In summary&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Given&lt;/strong&gt; steps establish state;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt; steps perform an action;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then&lt;/strong&gt; steps make assertions about state.&lt;/p&gt;
&lt;p&gt;Here's a minimal but complete example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;Given &lt;/span&gt;&lt;span class="nf"&gt;I am logged in as a user&lt;/span&gt;
&lt;span class="k"&gt;When &lt;/span&gt;&lt;span class="nf"&gt;I load https://google.com/&lt;/span&gt;
&lt;span class="k"&gt;Then &lt;/span&gt;&lt;span class="nf"&gt;the search input field is displayed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Eliminate abiguity.  Favour simplicity.  Never use more words if it can be adequately expressed with fewer.&lt;/p&gt;</content><category term="misc"/><category term="software"/><category term="gherkin"/></entry><entry><title>The ultimate AeroPress technique</title><link href="https://www.hugobaldwin.com/the-ultimate-aeropress-technique/" rel="alternate"/><published>2017-03-16T00:00:00+00:00</published><updated>2017-03-16T00:00:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2017-03-16:/the-ultimate-aeropress-technique/</id><summary type="html">&lt;p&gt;&lt;img alt="AeroPress!" src="/images/aeropress.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Having made countless cups of AeroPress'd coffee (mostly &lt;a href="https://catalyst.net.nz/"&gt;at work&lt;/a&gt;), I've finally arrived at the perfect cuppa.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Boil the jug.  Yup, boil.  You'll see why in a moment.&lt;/li&gt;
&lt;li&gt;Pour boiling water into your coffee cup.  If you take milk in your coffee leave room for milk.  Otherwise fill it up.&lt;/li&gt;
&lt;li&gt;Grind coffee / put ground coffee in an inverted AeroPress.&lt;/li&gt;
&lt;li&gt;Fill AeroPress with hot water from cup.  Your cup is now hot and the water has cooled to the perfect temperature &amp;#9758;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="AeroPress!" src="/images/aeropress.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Having made countless cups of AeroPress'd coffee (mostly &lt;a href="https://catalyst.net.nz/"&gt;at work&lt;/a&gt;), I've finally arrived at the perfect cuppa.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Boil the jug.  Yup, boil.  You'll see why in a moment.&lt;/li&gt;
&lt;li&gt;Pour boiling water into your coffee cup.  If you take milk in your coffee leave room for milk.  Otherwise fill it up.&lt;/li&gt;
&lt;li&gt;Grind coffee / put ground coffee in an inverted AeroPress.&lt;/li&gt;
&lt;li&gt;Fill AeroPress with hot water from cup.  Your cup is now hot and the water has cooled to the perfect temperature.&lt;/li&gt;
&lt;li&gt;Stir coffee in AeroPress.&lt;/li&gt;
&lt;li&gt;Screw filter &amp;amp; cap onto AeroPress, leave for a minute or two.&lt;/li&gt;
&lt;li&gt;Pour milk into cup.&lt;/li&gt;
&lt;li&gt;Place AeroPress right way up on the cup.&lt;/li&gt;
&lt;li&gt;Slowly plunge the coffee into the cup.&lt;/li&gt;
&lt;li&gt;Pop out used coffee grounds, rinse rubber seal, enjoy coffee.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This way the water can be used straight after boiling without waiting for it to cool to 85-90°, and you can warm your cup at the same time without wasting water.&lt;/p&gt;</content><category term="misc"/><category term="aeropress"/><category term="coffee"/></entry><entry><title>New project: CourseMonger.com</title><link href="https://www.hugobaldwin.com/new-project-coursemongercom/" rel="alternate"/><published>2017-03-13T00:00:00+00:00</published><updated>2017-03-13T00:00:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2017-03-13:/new-project-coursemongercom/</id><summary type="html">&lt;p&gt;So! I have a new project underway, &lt;a href="http://www.coursemonger.com"&gt;CourseMonger.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unlike many solutions, this one started with a problem.  My partner teaches parenting courses, each comprised of multiple workshops.  Each workshop can be at a different price and vary in number of places available.  Any prospective system would also need to provide a reservation system whereby a place is reserved for say, 8 minutes while a student completes her booking.&lt;/p&gt;
&lt;p&gt;Eventbrite provides registration-locking, but the Eventbrite model is based on &lt;em&gt;events &amp;#9758;&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;So! I have a new project underway, &lt;a href="http://www.coursemonger.com"&gt;CourseMonger.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unlike many solutions, this one started with a problem.  My partner teaches parenting courses, each comprised of multiple workshops.  Each workshop can be at a different price and vary in number of places available.  Any prospective system would also need to provide a reservation system whereby a place is reserved for say, 8 minutes while a student completes her booking.&lt;/p&gt;
&lt;p&gt;Eventbrite provides registration-locking, but the Eventbrite model is based on &lt;em&gt;events&lt;/em&gt;, many people at a specific location and time at perhaps a few different pricepoints (tickets).  Like a concert or sports event.  It lacks the ability to setup multiple classes per event or have per-class availability &amp;amp; pricing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="CourseMonger.com" src="/images/coursemonger_landing_page.png"&gt;&lt;/p&gt;
&lt;p&gt;Wouldn't it be nice if there was something designed specifically for the needs of small classes?  For parenting workshops, yoga classes, dance practice, martial arts training, etc?&lt;/p&gt;
&lt;p&gt;CourseMonger allows you to create courses comprised of multiple classes.  Each class can have its own title and optional description, spaces available, price and start and finish times.&lt;/p&gt;
&lt;p&gt;Once created, your course can be distributed to students as a single course registration page listing all the classes you run.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.coursemonger.com"&gt;Give it a try!&lt;/a&gt;  Email me directly with suggested features or bugs using &lt;a href="mailto:support@coursemonger.com"&gt;support@coursemonger.com&lt;/a&gt;, I'd love to hear from you.&lt;/p&gt;</content><category term="misc"/><category term="startup"/><category term="project"/><category term="bootstrap"/></entry><entry><title>How to create many-to-many relationships with associated properties in SQLAlchemy</title><link href="https://www.hugobaldwin.com/how-to-create-many-to-many-relationships-with-associated-properties-in-sqlalchemy/" rel="alternate"/><published>2016-06-05T00:00:00+01:00</published><updated>2016-06-05T00:00:00+01:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2016-06-05:/how-to-create-many-to-many-relationships-with-associated-properties-in-sqlalchemy/</id><summary type="html">&lt;p&gt;I once had a scenario where one business could be managed by multiple users, and each user could manage multiple businesses.  To further complicate matters, only one user could be the Administrator of a business, which should ideally also be represented by the relationship.&lt;/p&gt;
&lt;p&gt;Finding out how to represent that relationship was tricky as I couldn't find it in the SQLAlchemy docs, and StackOverflow was a mixed bag.  This is what I pieced together:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dt &amp;#9758;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;I once had a scenario where one business could be managed by multiple users, and each user could manage multiple businesses.  To further complicate matters, only one user could be the Administrator of a business, which should ideally also be represented by the relationship.&lt;/p&gt;
&lt;p&gt;Finding out how to represent that relationship was tricky as I couldn't find it in the SQLAlchemy docs, and StackOverflow was a mixed bag.  This is what I pieced together:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; \
    &lt;span class="n"&gt;SmallInteger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;constants&lt;/span&gt;


&lt;span class="c1"&gt;# Establish many-to-many relationship between users and (business) businesses&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserBusiness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;userbusiness&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;users.id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;business_id&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;businesses.id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                           &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;administrator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;business&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Business&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;users&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;businesses&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;users&amp;#39;&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;last_auth&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SmallInteger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;constants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# timestamps&lt;/span&gt;
    &lt;span class="n"&gt;update_date&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onupdate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;insert_date&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Business&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;businesses&amp;#39;&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SmallInteger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;constants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# timestamps&lt;/span&gt;
    &lt;span class="n"&gt;update_date&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onupdate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;insert_date&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In a Flask application, this would be contained your &lt;code&gt;models.py&lt;/code&gt; file.
Python's &lt;code&gt;__repr()__&lt;/code&gt; and other common functions omitted for clarity.&lt;/p&gt;
&lt;p&gt;To record a new User-Business relationship, first create the UserBusiness relationship, then add in the User and Business objects (if this were Flask, this'd likely be in &lt;code&gt;views.py&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserBusiness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;administrator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Business&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Acme Widgets Ltd&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To get a list of Businesses a user manages, you can do something like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;businesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ub&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;businesses&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ub&lt;/code&gt; here represents the UserBusiness relation.  You could then extend it to only get businesses which the user is an administrator of:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;businesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ub&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;businesses&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;administrator&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's all beautifully simple once you've done it successfully once or twice...&lt;/p&gt;</content><category term="misc"/><category term="SQLAlchemy"/><category term="Python"/><category term="database"/></entry><entry><title>What's your capacity to hustle?</title><link href="https://www.hugobaldwin.com/whats-your-capacity-to-hustle/" rel="alternate"/><published>2016-05-29T00:00:00+01:00</published><updated>2016-05-29T00:00:00+01:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2016-05-29:/whats-your-capacity-to-hustle/</id><summary type="html">&lt;p&gt;This gem turned up in the Ribbonfarm post &lt;em&gt;&lt;a href="https://www.ribbonfarm.com/2013/01/31/stone-soup-for-the-capitalists-soul/"&gt;Stone-Soup for the Capitalist’s Soul&lt;/a&gt;&lt;/em&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your rights to your wealth do not extend beyond your understanding of it.&lt;/li&gt;
&lt;li&gt;You are fair game for hustlers who understand your wealth better than you do.&lt;/li&gt;
&lt;li&gt;The role of the state is to limit the violence with which you can be hustled.&lt;/li&gt;
&lt;li&gt;If you are poor you have the right to hustle the rich within certain limits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's a take on the old stone-soup fable &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;This gem turned up in the Ribbonfarm post &lt;em&gt;&lt;a href="https://www.ribbonfarm.com/2013/01/31/stone-soup-for-the-capitalists-soul/"&gt;Stone-Soup for the Capitalist’s Soul&lt;/a&gt;&lt;/em&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your rights to your wealth do not extend beyond your understanding of it.&lt;/li&gt;
&lt;li&gt;You are fair game for hustlers who understand your wealth better than you do.&lt;/li&gt;
&lt;li&gt;The role of the state is to limit the violence with which you can be hustled.&lt;/li&gt;
&lt;li&gt;If you are poor you have the right to hustle the rich within certain limits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's a take on the old stone-soup fable in which a hungry soldier returning from war asks an old woman for food and is denied.  He then tells her he can make a magnificent soup using only a stone and hot water.  The old woman is curious and asks him to show her.&lt;/p&gt;
&lt;p&gt;Between tastes, he tells the woman the soup is coming along nicely; "however it would be even better if only they had a little &amp;lt;&lt;em&gt;insert ingredient&lt;/em&gt;&amp;gt;".  In this way he persuades her to part with more ingredients until he has prepared a great meal for them both.&lt;/p&gt;
&lt;p&gt;Ultimately the soldier leaves and the old woman realises it's just a normal stone.&lt;/p&gt;
&lt;p&gt;Venkatesh doesn't tell you what to do with his writing.  It's probably best left as an exercise for the reader.  Gotta have some skin in the game after all.&lt;/p&gt;</content><category term="misc"/><category term="ribbonfarm"/><category term="venkatesh"/><category term="hustling"/></entry><entry><title>Installing Apache Cordova on BunsenLabs Linux</title><link href="https://www.hugobaldwin.com/installing-apache-cordova-on-bunsenlabs-linux/" rel="alternate"/><published>2016-02-27T00:00:00+00:00</published><updated>2016-02-27T00:00:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2016-02-27:/installing-apache-cordova-on-bunsenlabs-linux/</id><summary type="html">&lt;p&gt;&lt;img alt="Apache Cordova header image" src="/images/cordova.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Installing &lt;a href="https://cordova.apache.org/"&gt;Cordova&lt;/a&gt; wasn't straightforward, so I wrote up what worked for me.&lt;/p&gt;
&lt;p&gt;First &lt;a href="https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions"&gt;install Nodejs&lt;/a&gt; and create a symlink because it gets installed as nodejs rather than node.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-sL&lt;span class="w"&gt; &lt;/span&gt;https://deb.nodesource.com/setup_5.x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;bash&lt;span class="w"&gt; &lt;/span&gt;-
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;nodejs
sudo&lt;span class="w"&gt; &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/nodejs&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/node
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;NPM is thankfully in the debian repos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;build-essential
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Install Cordova using NPM and &lt;a href="https://cordova.apache.org/docs/en/latest/guide/cli/index.html"&gt;create your first project&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;npm &amp;#9758;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Apache Cordova header image" src="/images/cordova.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Installing &lt;a href="https://cordova.apache.org/"&gt;Cordova&lt;/a&gt; wasn't straightforward, so I wrote up what worked for me.&lt;/p&gt;
&lt;p&gt;First &lt;a href="https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions"&gt;install Nodejs&lt;/a&gt; and create a symlink because it gets installed as nodejs rather than node.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-sL&lt;span class="w"&gt; &lt;/span&gt;https://deb.nodesource.com/setup_5.x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;bash&lt;span class="w"&gt; &lt;/span&gt;-
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;nodejs
sudo&lt;span class="w"&gt; &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/nodejs&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/node
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;NPM is thankfully in the debian repos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;build-essential
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Install Cordova using NPM and &lt;a href="https://cordova.apache.org/docs/en/latest/guide/cli/index.html"&gt;create your first project&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;cordova

cordova&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;MyProject
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;MyProject

platform&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;browser
platform&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;cordova run browser&lt;/code&gt; command requires &lt;code&gt;google-chrome&lt;/code&gt; so I installed chromium then symlinked google-chrome to it (you can skip this step if you use Chrome):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;chromium
sudo&lt;span class="w"&gt; &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/chromium&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/google-chrome
cordova&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;browser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ta-da!  You can now see your project running in the browser, such as it is.&lt;/p&gt;
&lt;p&gt;Install the &lt;a href="https://developer.android.com/sdk/installing/index.html"&gt;Android Stand-Alone SDK tools&lt;/a&gt;, then set the ANDROID_HOME environment variable and add the Android tools to your PATH:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;nano&lt;span class="w"&gt; &lt;/span&gt;~/.bash_profile
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/bin/android-sdk-linux
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;/tools
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While &lt;a href="https://help.ubuntu.com/community/EnvironmentVariables"&gt;this article was helpful&lt;/a&gt; I was ultimately unable to make the new environment variables persist over reboots, no matter which file I added them to (.bash_profile, .profile, .pam_environment, etc).&lt;/p&gt;
&lt;p&gt;Now try and build your new app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cordova&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you get an error about being unable to run &lt;code&gt;java -version&lt;/code&gt;, you may need to install the JDK and add the JAVA_HOME environment variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;default-jdk
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/jvm/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you get the following error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:CordovaLib:processDebugResources FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task &amp;#39;:CordovaLib:processDebugResources&amp;#39;.
&amp;gt; A problem occurred starting process &amp;#39;command &amp;#39;/home/windlab/bin/android-sdk-linux/build-tools/23.0.2/aapt&amp;#39;&amp;#39;

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 2.785 secs
ERROR building one of the platforms: Error code 1 for command: /home/windlab/projects/AlarmClock/platforms/android/gradlew with args: cdvBuildDebug,-b,/home/windlab/projects/AlarmClock/platforms/android/build.gradle,-Dorg.gradle.daemon=true,-Pandroid.useDeprecatedNdk=true
You may not have the required environment or OS to build this project
Error: Error code 1 for command: /home/windlab/projects/AlarmClock/platforms/android/gradlew with args: cdvBuildDebug,-b,/home/windlab/projects/AlarmClock/platforms/android/build.gradle,-Dorg.gradle.daemon=true,-Pandroid.useDeprecatedNdk=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's because you're trying to run a 32bit file on 64bit system.  &lt;a href="https://stackoverflow.com/a/27734148"&gt;Thanks Abdelraman Elkady&lt;/a&gt;!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;lib32stdc++6
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;lib32z1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, run the following command to display your new app in the Android emulator (if you haven't created one already the error surprisingly gives useful instructions):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cordova&lt;span class="w"&gt; &lt;/span&gt;emulate&lt;span class="w"&gt; &lt;/span&gt;android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"/><category term="linux"/><category term="cordova"/><category term="android"/></entry><entry><title>Calling the Top</title><link href="https://www.hugobaldwin.com/calling-the-top/" rel="alternate"/><published>2014-11-12T22:41:00+00:00</published><updated>2014-11-12T22:41:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2014-11-12:/calling-the-top/</id><summary type="html">&lt;p&gt;Some time mid-2013 I was reading through &lt;a href="https://bitcointalk.org/index.php?action=profile;u=68520;sa=showPosts"&gt;rpietila's posts&lt;/a&gt; on the bitcointalk.org forums and came across the theory that you could "call the top" of a bubble simply by selling if the price doubles in 7 days.&lt;/p&gt;
&lt;p&gt;Unfortunately, he's got 4.7k-odd posts now, so the exact post has eluded me so far.&lt;/p&gt;
&lt;p&gt;While the old trope "past performance doesn't indicate future results" always applies, there's no harm in being prepared to take advantage of an identified pattern should &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Some time mid-2013 I was reading through &lt;a href="https://bitcointalk.org/index.php?action=profile;u=68520;sa=showPosts"&gt;rpietila's posts&lt;/a&gt; on the bitcointalk.org forums and came across the theory that you could "call the top" of a bubble simply by selling if the price doubles in 7 days.&lt;/p&gt;
&lt;p&gt;Unfortunately, he's got 4.7k-odd posts now, so the exact post has eluded me so far.&lt;/p&gt;
&lt;p&gt;While the old trope "past performance doesn't indicate future results" always applies, there's no harm in being prepared to take advantage of an identified pattern should it manifest again.&lt;/p&gt;
&lt;p&gt;For an example from the most recent doubling-in-7-days, see this chart of the Nov 2013 run up to Dec 31st peak, where 7-day price doublings can be seen twice:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://bitcoincharts.com/charts/bitstampUSD#rg360zig6-hourzczsg2013-11-03zeg2013-12-05ztgOzm1g10zm2g25"&gt;Bitcoin charts link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In light of the recent upward price movement (THIS IS GENTLEMEN), the topic seemed apposite, so I whipped up a script to test the theory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# if bitcoin price on Bitstamp doubles within a 7-day timeframe, sell&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.bitstamp.net/api/&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;seven_day_history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="n"&gt;CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;API_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&amp;#39;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_nonce&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1970&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_private_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_nonce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}{}{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;API_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digestmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;signature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;nonce&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;make_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ticker/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# append time stamp and price of bitcoin to list&lt;/span&gt;
    &lt;span class="n"&gt;seven_day_history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;

    &lt;span class="c1"&gt;# remove items older than 7 days&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seven_day_history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;seven_day_history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;d1_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seven_day_history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;d7_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seven_day_history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# sell all available bitcoin if price doubled in last 7 days&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d7_price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;d1_price&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_private_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;balance/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;xbt_available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;btc_available&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# place limit order at price of highest bid&lt;/span&gt;
        &lt;span class="n"&gt;sell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_private_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sell/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;xbt_available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                &lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d7_price&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;: Placed order id &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; to sell &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; XBT at &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; each.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sell&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;datetime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sell&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sell&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sell&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# sleep for 12 hours&lt;/span&gt;
    &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;43200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/byzantime/2bcacc490ec96ed483a5"&gt;View on Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To use this, you will need a funded Bitstamp account with an API key setup allowing the script to query balance and place sell orders.
Suggested improvements would include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;increasing the price checking frequency - &lt;code&gt;sleep(21600)&lt;/code&gt; would give you a 6hr resolution&lt;/li&gt;
&lt;li&gt;message logging / email sending&lt;/li&gt;
&lt;li&gt;replace scheduling by running script as cron job, with celery, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mandatory disclaimer:
The code is licensed under the &lt;a href="https://opensource.org/licenses/MIT"&gt;MIT license&lt;/a&gt; and does not claim to be fit for any purpose.&lt;/p&gt;</content><category term="misc"/><category term="bitcoin"/><category term="python"/><category term="requests"/><category term="trading"/></entry><entry><title>blogging with Pelican and Github pages</title><link href="https://www.hugobaldwin.com/blogging-with-pelican-and-github-pages/" rel="alternate"/><published>2014-11-12T21:40:00+00:00</published><updated>2014-11-12T21:40:00+00:00</updated><author><name>Hugo Baldwin</name></author><id>tag:www.hugobaldwin.com,2014-11-12:/blogging-with-pelican-and-github-pages/</id><summary type="html">&lt;p&gt;Hey! Welcome to my new blog.&lt;/p&gt;
&lt;p&gt;So, when it came to setting up a blog again, I didn't want to go the old route to Livejournal / Blogger / Tumblr.  Too much Y2K, Google, and teenage girl angst, respectively.
Svbtle &amp;amp; Ghost cost money, and Medium's focus is on the platform, not you.  That's when I remembered Github pages allows you to setup a free static site.&lt;/p&gt;
&lt;p&gt;First create a &lt;a href="https://github.com/new"&gt;new Github pages repo&lt;/a&gt; called &lt;em&gt;username&lt;/em&gt;.github.io, where &lt;em&gt;username&lt;/em&gt; is your Github &amp;#9758;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hey! Welcome to my new blog.&lt;/p&gt;
&lt;p&gt;So, when it came to setting up a blog again, I didn't want to go the old route to Livejournal / Blogger / Tumblr.  Too much Y2K, Google, and teenage girl angst, respectively.
Svbtle &amp;amp; Ghost cost money, and Medium's focus is on the platform, not you.  That's when I remembered Github pages allows you to setup a free static site.&lt;/p&gt;
&lt;p&gt;First create a &lt;a href="https://github.com/new"&gt;new Github pages repo&lt;/a&gt; called &lt;em&gt;username&lt;/em&gt;.github.io, where &lt;em&gt;username&lt;/em&gt; is your Github username.
Create a new folder locally to store the site files (I like to keep website files tidy in &lt;code&gt;/var/www/&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/var/www/username.github.io
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/var/www/username.github.io
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/username/username.github.io
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To save time on building a site, I first tried using &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;, but found the instructions unclear and the setup seemed needlessly complex.  Apart from that, why is it so difficult to install anything Node related on Debian (though a friend has the same issue on Fedora, so maybe it's a Node thing)?&lt;/p&gt;
&lt;p&gt;Then I found &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt;, which allows you to write content using markdown and renders site templates into HTML using &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja&lt;/a&gt;.  It's also much easier to install and get running!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pelican
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Site content goes in a &lt;code&gt;content/&lt;/code&gt; folder inside the pelican install directory, and individual pages start with simple metadata:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Title: My super title
Date: 2010-12-03 10:20
Modified: 2010-12-05 19:30
Category: Python
Tags: pelican, publishing
Slug: my-super-post
Authors: Alexis Metaireau, Conan Doyle
Summary: Short version for index and feeds

This is the content of my super blog post.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To preview the site locally, run a Python web server, then check &lt;a href="http://localhost:8000"&gt;localhost:8000&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;output
python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;SimpleHTTPServer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To publish your content live, back in the main site directory run the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pelican
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;output
git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--all
git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my first commit&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course, there's lots of other things you can do, like changing or customising the default theme.  &lt;a href="https://docs.getpelican.com/en/3.5.0/"&gt;Read the docs here&lt;/a&gt; (wish more projects used &lt;a href="https://readthedocs.org/"&gt;read the docs&lt;/a&gt;!&lt;/p&gt;</content><category term="misc"/><category term="pelican"/><category term="hello world"/><category term="github"/></entry></feed>