useOnEnter

import React, { useContext, useEffect } from 'react';
import { StdinContext } from 'ink';

export default function useOnEnter(onEnter) {
  const { stdin } = useContext(StdinContext);

  useEffect(() => {
    const onData = data => {
      const s = data.toString();

      if (s === '\r') {
        onEnter();
      }
    };

    stdin.on('data', onData);

    return () => {
      stdin.off('data', onData);
    };
  });
};

Removed from brookjs

const child$s = new WeakMap();

const getChildStream = (stream$, id) => {
    let childs = child$s.get(stream$);

    if (!childs) {
        child$s.set(stream$, childs = {});
    }

    if (childs[id]) {
        return childs[id];
    }

    return childs[id] = stream$.map(props => props.dict[id]);
};

const orderMatches = (prev, next) => prev.order === next.order;
const id = x => x;

export default function loop(mapper, callback) {
    if (callback == null) {
        callback = mapper;
        mapper = id;
    }

    return stream$ => {
        const src$ = stream$.map(mapper);

        return src$.skipDuplicates(orderMatches)
            .map(props => props.order.map(id => callback(getChildStream(src$, id), id)));
    };
}
import Kefir from 'kefir';
import * as PropTypes from 'prop-types';

const spyOn = (s$, validator, propName, componentName, name) => {
    if (s$._alive) {
        const handler = (event) => {
            if (event.type === 'value') {
                PropTypes.checkPropTypes(validator, event.value, 'prop', componentName);
            }
        };
        if (!s$._spyHandlers) {
            s$._spyHandlers = [];
        }
        s$._spyHandlers.push({ name, handler });
        s$._dispatcher.addSpy(handler);

        if (s$._currentEvent) {
            handler(s$._currentEvent);
        }
    }
};

export default function observableOfValidator(valueValidator, name = 'observableOf') {
    const validator = function observableOf(props, propName, componentName) {
        const propValue = props[propName];
        if (propValue == null) {
            return null;
        }

        if (!(propValue instanceof Kefir.Observable)) {
            return new TypeError(`${componentName}: ${propName} must be an Observable, got "${typeof propValue}"`);
        }

        spyOn(propValue, valueValidator, propName, componentName, name);

        return null;
    };

    validator.isRequired = function andIsRequired(props, propName, componentName) {
        const propValue = props[propName];

        if (!(propValue instanceof Kefir.Observable)) {
            return new TypeError(`${componentName}: ${propName} must be an Observable, got "${typeof propValue}"`);
        }

        spyOn(propValue, valueValidator, propName, componentName, name);

        return null;
    };

    return wrapValidator(validator, name);
}

function wrapValidator(validator, typeName, typeChecker = null) {
    return Object.assign(validator.bind(), {
        typeName,
        typeChecker,
        isRequired: Object.assign(validator.isRequired.bind(), {
            typeName,
            typeChecker,
            typeRequired: true,
        }),
    });
}

Sync ownCloud bookmarks to Pinboard

import os
import requests
from bs4 import BeautifulSoup
from termcolor import cprint
from dotenv import load_dotenv


class Hydrator(object):
    def __init__(self):
        self.soups = {}

    def title(self, url):
        soup = self._get_soup(url)
        title = soup.title.string

        if title == "":
            cprint(f"Url provided as title for {url}", "white", "on_blue")
            title = url

        return title

    def description(self, url):
        soup = self._get_soup(url)
        meta = soup.find_all("meta")

        for tag in meta:
            if (
                "name" in tag.attrs.keys()
                and tag.attrs["name"].strip().lower() == "description"
            ):
                content = tag.attrs.get("content")

                if content is None:
                    content = tag.attrs.get("value")

                if content is None:
                    cprint(f"No content found", "red")
                    print(tag.attrs.keys())
                else:
                    return content

        cprint(f"No description found for {url}", "white", "on_blue")

        return ""

    def _get_soup(self, url):
        soup = self.soups.get(url)

        if soup is None:
            response = requests.get(url)
            soup = self.soups[url] = BeautifulSoup(response.content, "lxml")

        return soup


hydrate = Hydrator()


def get_bookmark_from_pinboard(url):
    response = requests.get(
        url="https://api.pinboard.in/v1/posts/get",
        params={
            "url": url,
            "auth_token": os.getenv("PINBOARD_AUTH_TOKEN"),
            "format": "json",
        },
    )

    posts = response.json()["posts"]

    return posts[0] if len(posts) == 1 else None


def hydrate_bookmark(bookmark):
    title = bookmark["title"]
    url = bookmark["url"]
    description = bookmark["description"]

    if title == url or title == "":
        title = hydrate.title(url)

    if description == url or description == "":
        description = hydrate.description(url)

    return {
        "url": url,
        "title": title,
        "description": description,
        "tags": bookmark["tags"],
    }


def add_bookmark_to_pinboard(bookmark):
    response = requests.get(
        url="https://api.pinboard.in/v1/posts/add",
        params={
            "auth_token": os.getenv("PINBOARD_AUTH_TOKEN"),
            "format": "json",
            "url": bookmark["url"],
            # actually title
            "description": bookmark["title"],
            # actually description
            "extended": bookmark["description"],
            "tags": ",".join(bookmark["tags"]),
        },
    )

    body = response.json()

    return body["result_code"] == "done"


def process_bookmark(bookmark):
    url = bookmark["url"]
    pinboard_bookmark = get_bookmark_from_pinboard(url)

    # Add only if we don't find the bookmark in pinboard.
    if pinboard_bookmark is None:
        hydrated = hydrate_bookmark(bookmark)
        success = add_bookmark_to_pinboard(hydrated)

        if success:
            cprint(f"Successfully added {url} to pinboard", "green")
        else:
            cprint(f"Error adding {url} to pinboard", "red")
    else:
        cprint(f"Url {url} found in Pinboard. Skipping.", "yellow")


def send_request(page=0):
    try:
        response = requests.get(
            url=f'https://{os.getenv("OWNCLOUD_DOMAIN")}/index.php/apps/bookmarks/bookmark',
            params={"type": "bookmark", "page": page},
            headers={
                "Cookie": os.getenv("OWNCLOUD_COOKIE"),
                "requesttoken": os.getenv("OWNCLOUD_REQUEST_TOKEN"),
            },
        )

        body = response.json()
        status = body["status"]

        if status == "success":
            data = body["data"]

            for bookmark in data:
                process_bookmark(bookmark)

            if len(data) != 0:
                send_request(page + 1)
    except requests.exceptions.RequestException as err:
        cprint(f"Request failed: {err.request.url}", "red")


if __name__ == "__main__":
    load_dotenv()
    send_request()
PINBOARD_AUTH_TOKEN=username:token
OWNCLOUD_DOMAIN=your.owncloud.domain.com
OWNCLOUD_COOKIE=Get this from the network tab of the bookmarks page
OWNCLOUD_REQUEST_TOKEN=Also from the network tab

Update Array Hack

const arr = [1, 2, 3];
const newArr = Array.from({ ...arr, 1: 3, length: arr.length });
console.log(newArr); // [1, 3, 3]
const arr = [1, 2, 3];
const newObj = { ...arr };
console.log(newObj); // {0: 1, 1: 2, 2: 3}
const arr = [1, 2, 3];
const newObj = { ...arr, 1: 3 };
console.log(newObj); // {0: 1, 1: 3, 2: 3}

Trellis Gatsby Configuration

diff --git a/deploy-hooks/build-before.yml b/deploy-hooks/build-before.yml
--- a/deploy-hooks/build-before.yml
+++ b/deploy-hooks/build-before.yml
+- name: Install npm dependencies
+  command: npm ci
+  connection: local
+  args:
+    chdir: "~/path/to/gatsby"
+
+- name: Compile assets for production
+  command: npm run build
+  connection: local
+  args:
+    chdir: "~/path/to/gatsby"
+
+- name: Copy production assets
+  synchronize:
+    src: "~/path/to/gatsby/public"
+    dest: "{{ deploy_helper.new_release_path }}"
+    group: no
+    owner: no
+    rsync_opts: --chmod=Du=rwx,--chmod=Dg=rx,--chmod=Do=rx,--chmod=Fu=rw,--chmod=Fg=r,--chmod=Fo=r
diff --git a/group_vars/development/wordpress_sites.yml b/group_vars/development/wordpress_sites.yml
--- a/group_vars/development/wordpress_sites.yml
+++ b/group_vars/development/wordpress_sites.yml
@@ -8,6 +8,7 @@ wordpress_sites:
       - canonical: domain.test
     local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root)
     admin_email: admin@domain.test
+    nginx_wordpress_site_conf: templates/domain.com.conf.j2
     multisite:
       enabled: false
     ssl:
diff --git a/group_vars/production/wordpress_sites.yml b/group_vars/production/wordpress_sites.yml
index 48b69b9..99d775f 100644
--- a/group_vars/production/wordpress_sites.yml
+++ b/group_vars/production/wordpress_sites.yml
@@ -9,6 +9,7 @@ wordpress_sites:
     local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root)
     repo: git@bitbucket.org:repo/domain.git # replace with your Git repo URL
     branch: master
+    nginx_wordpress_site_conf: templates/domain.com.conf.j2
     multisite:
       enabled: false
     ssl:
diff --git a/templates/domain.com.conf.j2 b/templates/domain.com.conf.j2
+++ b/templates/domain.com.conf.j2
+{% extends 'roles/wordpress-setup/templates/domain.com.conf.j2' %}
+
+{% block location_primary -%}
+location /wp-json {
+    try_files $uri $uri/ /index.php?$args;
+  }
+  location /wp/ {
+    try_files $uri $uri/ /wp/wp-admin/;
+  }
+  location / {
+    root       {{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/public;
+    error_page 404 /404.html;
+  }
+{% endblock %}

Get List of Commits

const { findGitRepos, getCommitsFromRepos } = require ('./gitutils');

findGitRepos(['~/Code/Valtech'], 5, (err, result) => {
    if (err) throw err;

    getCommitsFromRepos(result, 30, (err, result) => {
        if (err) throw err;

        console.log(result);
    });
});
// Stolen from here:
// https://github.com/notwaldorf/tiny-care-terminal/blob/78e038069f01c36148d7d486d7775275d3df1df8/gitbot.js
const resolve = require('resolve-dir');
const subdirs = require('subdirs');
const isGit = require('is-git');
const gitlog = require('gitlog');
const path = require('path');
const async = require("async");
const git = require('git-utils');

try {
  const gitUsername = require('git-user-name')();
} catch(err) {
  console.error(`ERROR reading git-config.
    Use e.g. 'git config --global user.name "Mona Lisa"'.
    See 'man git config' for further information.
  `);
  return process.exit(0);
}

/**
 * Go through all `repos` and look for subdirectories up to a given `depth`
 * and look for repositories.
 * Calls `callback` with array of repositories.
 */
function findGitRepos(repos, depth, callback) {
  let allRepos = [];
  async.each(repos, (repo, repoDone) => {
    repo = resolve(repo);
    subdirs(repo, depth, (err, dirs) => {
      if (err) {
        switch (err.code) {
          case 'ENOENT':
            return callback(`Could not open directory directory: ${err.path}n`, null);
          case 'EACCES':
            return; //ignore if no access
          default:
            return callback(`Error "${err.code}" doing "${err.syscall}" on directory: ${err.path}n`, null);
        }
      }
      if (dirs) dirs.push(repo);
      async.each(dirs, (dir, dirDone) => {
        isGit(dir, (err, isGit) => {
          if (err) {
            return callback(err, null);
          }
          if (!dir.includes('.git') && isGit) {
            allRepos.push(dir);
          }
          dirDone();
        });
      }, repoDone);
    });
  }, err => {
    callback(err, allRepos.sort().reverse());
  });
}

/**
 * returns all commits of the last given `days`.
 * Calls `callback` with line-seperated-strings of the formatted commits.
 */
function getCommitsFromRepos(repos, days, callback) {
  let cmts = [];
  async.each(repos, (repo, repoDone) => {
    let localGitUsername = '';
    try {
      const gitUtilsRepo = git.open(repo);
      localGitUsername = gitUtilsRepo.getConfigValue('user.name') || gitUsername;
    } catch (err) {
      localGitUsername = gitUsername;
    }
    try {
      gitlog({
        repo: repo,
        all: true,
        number: 100, //max commit count
        since: `${days} days ago`,
        fields: ['abbrevHash', 'subject', 'authorDate', 'authorName'],
        author: localGitUsername
      }, (err, logs) => {
        // Error
        if (err) {
          callback(`Oh noes­čś▒nThe repo ${repo} has failed:n${err}`, null);
        }
        // Find user commits
        let commits = [];
        logs.forEach(c => {
          // filter simple merge commits
          if (c.status && c.status.length)
            commits.push(`${c.abbrevHash} - ${c.subject} (${c.authorDate}) <${c.authorName.replace('@end@n','')}>`);
        });

        // Add repo name and commits
        if (commits.length >= 1) {
          // Repo name
          cmts.push(repo);
          cmts.push(...commits);
        }

        repoDone();
      });
    } catch(err) {
      callback(err, null);
    }
  }, err => {
    callback(err, cmts.length > 0 ? cmts.join('n') : "Nothing yet. Start small!");
  });
}

module.exports.findGitRepos = findGitRepos;
module.exports.getCommitsFromRepos = getCommitsFromRepos;

What Order Does This Execute In?

setTimeout(() => {
    console.log('inside timeout');
}, 0);

process.nextTick(() => {
    console.log('inside nextTick');
});

const p = new Promise(function(resolve, reject) {
    console.log('inside promise');

    resolve();
})
    .then(() => console.log('inside then'));

console.log('after promise');

elm-types Strawman

import t from 'elm-types';

const AddTodo = t.Constructor('AddTodo', [t.String]);
const RemoveTodo = t.Constructor('AddTodo', [t.String]);
const Action = t.Union('Action', [AddTodo]);
const Todo = t.Record('Todo', {
    name: t.String,
    completed: t.Bool
});
const Model = t.Record('Model', {
    todos: t.List('todos', Todo)
});

// Throws if function is not passed correct parameter types
const update = t.Function('update', [Model, Action, Model], (state, action) =>
    // Throws if last object is not exaustive
    t.case(action, Action, {
        [AddTodo]: add => ({
            ...state,
            todos: [...state.todos, Todo(add, false)]
        }),
        [RemoveTodo]: remove => ({
            ...state,
            todos: state.todos.filter(todo => todo.name === remove)
        })
    });
);

const store = createStore(update, Model([]));

const App = ({ todos }) => (
    <div>
        <h1>Todo List</h1>
        <ul>
            {todos.map(todo => (
                <li onClick={() => store.dispatch(RemoveTodo(todo.name))}>
                    {todo.name}
                </li>
            ))}
        </ul>
    </div>
);

const AppProps = t.Record('AppProps', {
    todos: t.List('todos', Todo)
});

App.propTypes = t.PropTypes(AppProps);

render(
    <App {...store.getState()} />,
    document.getElementById('app')
);

flatten two ways

function flatten(source, acc = []) {
    if (source.length === 0) return acc;

    const [head, ...tail] = source;

    return flatten(tail, [...acc, ...(Array.isArray(head) ? flatten(head) : [head])]);
}

console.log(flatten([1,2,[3,4,5,[6,7],8,[9]],10]));
import * as R from 'ramda';

const flatten = R.ifElse(
    R.propSatisfies(R.equals(0), 'length'),
    R.flip(R.identity),
    R.converge((source, acc = []) => flatten(source, acc), [
	    R.tail,
        R.useWith(R.flip(R.concat), [
	        R.pipe(R.head, R.ifElse(Array.isArray, head => flatten(head, []), R.of)),
	        R.identity
        ])
    ])
);

console.log(flatten([1,2,[3,4,5,[6,7],8,[9]],10], []));