MacBook, defective by design banner

Put the knife down and take a green herb, dude.


One feller's views on the state of everyday computer science & its application (and now, OTHER STUFF) who isn't rich enough to shell out for

Using 89% of the same design the blog had in 2001.

Back-up your data and, when you bike, always wear white.

As an Amazon Associate, I earn from qualifying purchases. Affiliate links in green.


MarkUpDown is the best Markdown editor for professionals on Windows 10.

It includes two-pane live preview, in-app uploads to imgur for image hosting, and MultiMarkdown table support.

Features you won't find anywhere else include...

You've wasted more than $15 of your time looking for a great Markdown editor.

Stop looking. MarkUpDown is the app you're looking for.

Learn more or head over to the 'Store now!

Friday, May 26, 2023

 Only Apple brings you amazingly architected solutions like this.

This is precisely that "happy path or no path" mentality that I've complained about before. If you were green fielding a new OS, there's no way you say, "I know where we'll put the setting for the default mail app! In OUR email app!"

I mean, it makes sense if is the only mail handler on your box. I'm guessing I can't delete it even though I don't use it, because if I could I wouldn't be able to select another client to replace it! I also bet the percentage of people using 3rd party email apps for their company email inside of the infinite loops is really low.

This is precisely why I wouldn't hate seeing Apple get split into a hardware-and-bios company and a separate software company or, better yet, companies. I don't hold out any hope for that... I think independent competition with OSes only happens if Linux ever finally commoditizes the desktop OS. Which is could do; I use Ubuntu regularly now. Which is also why it probably never will -- it's good enough for its user base, but still obviously not competitive enough for the typical end user.

In any event, Apple does a great job defining one clean-room use case for its products and, I assume, efficiently implementing that mvp. But it's not very creative at the next step: Coming up with potential real-world fail conditions once the mvp is running.

Labels: , , , ,

posted by ruffin at 5/26/2023 07:02:00 PM
Monday, April 17, 2023

You know what stinks? Sites like the Washington Times that throw so many advertisements into your face that you'll never actually get the information you want.

You know what would rule? Being able to have a personal killfile for URLs that stink when you go to a search site.

You can do this now on a search-by-search basis. Try this:

Now try this:

Very different results.

If we had enough people stop using, idk, (which just asked me to sign in and turn off my adblocker about four times, and not all at once, by which I mean I scroll and it asks again, and I read a while and it asks again; ARGH!!1!) or (which I often get in results when I want MDN... yes, I normally search with MDN in the terms now), maybe Google would learn not to include these things as high in the search rankings.

Just an idea. Guess I should write a browser extension.

Labels: ,

posted by ruffin at 4/17/2023 11:09:00 AM
Friday, April 07, 2023

So you know how to use git. You create a folder, smack in some files, git init, git add ., git commit -m "Initial commit". You don't even use a GUI tool. Madness.

Stop doing that. Well, not not [sic] using a GUI tool. Stop git add . first. Start with an empty commit initially.

Why? Because then you don't have a linear repo any more. You can start branches that go "backwards", essentially. This may seem insane, but, trust me, someday it will come in handy.

Great step-by-step by VonC:

You can even rebase your existing history on top of that empty commit (see "git: how to insert a commit as the first, shifting all the others?")

In both cases, you don't rely on the exact SHA1 value of that empty tree.
You simply follow a best practice, initializing your repo with a first empty commit.

To do that:

git init my_new_repo
cd my_new_repo
git config username
git config email@com
git commit --allow-empty -m "initial empty commit"

That will generate a commit with a SHA1 specific to your repo, username, email, date of creation (meaning the SHA1 of the commit itself will be different every time).
But the tree referenced by that commit will be 4b825dc642cb6eb9a060e54bf8d69288fbee4904, the empty tree SHA1.

git log --pretty=raw
commit 9ed4ff9ac204f20f826ddacc3f85ef7186d6cc14
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904      <====
author VonC <> 1381232247 +0200
committer VonC <> 1381232247 +0200
    initial empty commit

An interesting bit of trivia: Though the SHA of the commit will change from repo to repo, the tree's SHA will always be the same: 4b825dc642cb6eb9a060e54bf8d69288fbee4904.


posted by ruffin at 4/07/2023 01:12:00 PM
Saturday, March 25, 2023


Oddly enough, a documentary called "Code: Debugging the Gender Gap" would seem to dispute the notion that Clippy was popular with focus groups before its launch. Former Microsoft executive Roz Ho stated."

Most of the women thought the characters were too male and that they were leering at them. So we’re sitting in a conference room. There’s me and, I think, like, 11 or 12 guys, and we’re going through the results, and they said, ‘I don’t see it. I just don’t know what they’re talking about.’ And I said, ‘Guys, guys, look, I’m a woman, and I’m going to tell you, these animated characters are male-looking.’

Omg. They're absolutely right.

Be as open to seeing bias as you'd like, you will always have a blindspot. Until today, this was one of mine.

Labels: , , ,

posted by ruffin at 3/25/2023 06:14:00 PM
Friday, March 24, 2023

A couple of things to talk about today.

First, the new 16" Framework laptop seems awesome. It includes room for upgradeable discrete graphics and is configurable so you can include or remove a 10-key keypad and recenter your main keyboard if it's removed. The Framework really is about as close to the "let me build my laptop like I want" dream folks like me have had for a while.

And they have already delivered. I bagged the old 13" Framework laptop, refurb, for about $600, that comes without OS, SSD, RAM, WiFi card, or a power supply. I pulled those pieces out of the junk drawer from upgrades of other laptops and installed Ubuntu. See? It really is like building your own brandless DIY tower.

my half-length SSD with an extender
here's my half-length SSD installed with an extender

My first gen refurb has a nice screen, decent keyboard, plenty fast with the slowest 11th gen Intel i5 Framework has ever sold, but what was most important to me is that I might be able to replace the battery in a few years when it finally dies. Amazingly, starting yesterday, they're selling a better battery with over 10% more power for my laptop. And not that I need to upgrade, but all the new processors fit in my box too (13th gen Intel and AMD 7040s) two years after its release.

Small, though not the smallest. High quality components, but not the best (not a ThinkPad keyboard or a MacBook trackpad by any stretch). Not the best battery life, but much better than my gaming laptop's. Clearly privileges repairability above all else, but still does a good job everywhere else -- which is why I'm so impressed by the 16"'s plans for graphics. If you need that much GPU power, and they pull things off as well as they have so far, you should start saving some real dough when you upgrade.

What's to hate? The stupid user-configurable ports. They cost $9-19 a piece for USB or HDMI ports. There's a nice group of four refurb expansion cards for $29, but they're $9 to ship. Robbery, I tell you.

I get why -- in theory -- "pick your ports" is a neat idea, but get this: The 13" Framework is just a four-USB-C-port box. The expansion cards ultimately plug into USB-C ports. Though you can go directly to the USB-C ports on the motherboard without expansion cards, the "raw" ports are difficult to reach without the cards installed, and I've got one port on my motherboard that's a little loose already. I wonder if they're rated for the same number of usages.

Overheard [in my head]:

Look, you have to have at least one USB-C for power. Why not just put in two permanent USB-C ports on every laptop and save $18?

"Because then you lose your choice for one slot. Maybe I only want one USB-C like [the old, super-slim 12"] MacBook"

Oh sheesh, whatever, fine. Why not one USB-C port that's easy to reach built-in?

"Well, what if someone wants power on the other side of the laptop and two different ports on that side?"


That said, their "courage" parody showing they'll allow you to install SIX HEADPHONE PORTS on one 16" laptop is hilarious.

If you care about repairability and upgradability at all -- no, if you've complained about soldered on RAM and SSDs on MacBooks ­even once! -- you owe it to Framework to make it your next laptop. Refurbs like mine have dropped to $599... not a great price for BYO[RAM, SSD, WiFi, and power supply], as you can often buy a completely new gaming laptop with all those things for less (here's today's under $600 example).

But find me another well-made laptop with decent battery life that allows you to swap out an internal battery for $60.

Second, let me remind myself how to create and apply git patches.


To produce patch for several commits, you should use format-patch git command, e.g.

git format-patch -k --stdout R1..R2


Then in another repository apply the patch by am git command, e.g.

git am -3 -k file.patch

If things go completely sideways and you're, like me, applying a patch from a thumb drive where there's no internet and no great Plan B, you can apply a patch file by hand even if git gives you the "It does not apply to blobs recorded in its index." error.

I really like how git anticipates and supports completely disconnected edits. It makes it really easy to grab a few files, any laptop, take off somewhere to get some work done, and know you can export that work back wherever you started.

Labels: ,

posted by ruffin at 3/24/2023 09:49:00 PM
Monday, March 06, 2023

I know VIm has multiple registers, but I don't use them enough. I use some clipboard managers that essentially solve the same problem OS-wide (though the solution isn't as slick as VIm's), but there are still times where I need the same level of control with my "intra-vim" clipboards.

Here's a great overview from StackOverflow, edited slightly to use register a instead of k:

Registers in Vim let you run actions or commands on text stored within them. To access a register, you type "a before a command, where a is the name of a register. If you want to copy the current line into register a, you can type


Or you can append to a register by using a capital letter


You can then move through the document and paste it elsewhere using


I think the key for me was remembering that, in VIm-language, " is means select register and not pick an action with which to use a register. (I kept entering these patterns in reverse when I tried without looking the syntax back up.)

That is, the command is:

  1. " - select register
  2. a - name of register
  3. p - action (here, paste)


posted by ruffin at 3/06/2023 01:14:00 PM
Tuesday, February 07, 2023

Let's try to put LeBron's breaking Kareem's career points scoring record into perspective.

  • LeBron has 38,352 over 20 seasons.
  • Kareem has 38,387 over 20 seasons.
  • Jordan has 32,292 over 15 seasons.

And it's not like Jordan stopped early. He took off his 10th and most of his 11th season. Then he took off his 15th, 16th, and 17th seasons, played his 18th and 19th, and took off his 20th.

If we add those seasons back, what would we get?

Here's a way to unfairly estimate those seasons:

We'll take the last "fullish" season before each break and, if it exists, the next fullish season after he comes back and half the seasons' totals. Then we'll pretend that's what he would've scored during the missing seasons -- and for here, we're going to remove his partial 1994-5 season and replace it with this ballpark estimate.

  • In 92-3, Jordan had 2,541 points.
  • In 95-6, he had 2,491 points.

So for 93-4 and 94-5, we're going to add (2541 + 2491) / 2 per season for 5032 points for two seasons.

But we have to remove the points from his 17 games in 94-5, which is 457 points. Not fair, I know, but let's do it. Those could've been 17 easy games, or you could argue he could push harder not playing a full season or whatever. Better to use full season estimates. Maybe.

All told, that's plus 4575 points.

  • In 97-8, he had 2,357.
  • In 01-2, he only played 60 games and had 1,375 points.

That 01-2 amount is pretty conservative from the injuries, but let's use this ballpark measurement. That's (2357 + 1375) / 2 = 1866, but for three seasons.

That plus 5598 points.

Now let's get in that 20th season. This isn't fair the other way (to his benefit this time), but let's say he repeats his 19th.

  • That's 1,640 points (he amazingly plays all 82 at 37 mins a game)

So that's 4575 + 5598 + 1640 = 11813 points.

That gives Jordan 44105 points for 20 seasons.

Now that's insane. He's now 5753 ahead of LeBron (and 5718 over Kareem).

Once LeBron beats that, you can get back to me on GOAT arguments. (And you'll still lose.)

PS I'm sure I've messed up some math somewhere. Sometime I might correct it. Apologies in advance.

Labels: ,

posted by ruffin at 2/07/2023 11:26:00 PM
Wednesday, January 11, 2023

Remember when I said this?

but if I want something quick that can run anywhere [someone's on Windows], I use a batch file, and over the years, kinda like VIm, I've slowly become if not proficient then at least competent.

What a naïve time that was.

Take this seemingly innocuous question on SO:

How can you you insert a newline from your batch file output?

And then check this answer:

Here you go, create a .bat file with the following in it :

@echo off
REM Creating a Newline variable (the two blank lines are required!)
set NLM=^
set NL=^^^%NLM%%NLM%^%NLM%%NLM%
REM Example Usage:
echo There should be a newline%NL%inserted here.

Wha? set NL=^^^%NLM%%NLM%^%NLM%%NLM%??? What in the world? Huh?

Luckily (?) one of the commenters asked the same question.

Here's part of the answer:

The caret is an escape character for the next character, or at the line end it is used as multiline character, but this is nearly the same.

At the line end it simply escapes the next character, in this case the <Linefeed>, but there is a hidden feature, so if the escaped character is a <LF> it is ignored and the next character is read and escaped, but this charater will be always escaped, even if it is also a <LF>.


So you need to add an escaped linefeed into the line and that is the NL-Variable. The NL-Variable consists of only three characters.
NL=^<LF><LF> And if this is expanded, it creates only one escaped <LF> as the first <LF> after the caret will be ignored.

I mean, um, that requires Inception level cognition to understand. 😏 (Okay, actually it's pretty straightforward, but talk about a lack of discoverability.)

What's even more interesting is that dev's answer to the original question, which is this:

Like the answer of Ken, but with the use of the delayed expansion.

setlocal EnableDelayedExpansion
(set \n=^
%=Do not remove this line=%
echo Line1!\n!Line2
echo Works also with quotes "!\n!line2"


Okay, before I point out what I want to point out, note that %=_____=% is an inline comment format for batch.

From, a common domain when I'm in batchland:

Batch variable names can contain spaces and punctuation, we can take advantage of this fact by typing our comment as a non-existent variable. Because it doesn’t exist, this variable will expand to nothing and so will have no effect when the script is run.

n.b. This only works in batch files, not directly at the command prompt.

To be sure that we don’t accidently choose a name which is in fact a real variable, start and end the comment with "="

@Echo off
Echo This is an example of an %= Inline Comment =% in the middle of a line.

(Variable names starting with "=" are reserved for undocumented dynamic variables. Those dynamic variables never end with "=", so by using an "=" at both the start and end of our comment, there is no possibility of a name clash.)

And we're back

Okay, now what I'd like to point out here is the setlocal EnableDelayedExpansion, something I think I had to use recently in a script I wrote. It's a bizarre new world.

Here's some explanation from


Delayed Expansion will cause variables within a batch file to be expanded at execution time rather than at parse time[;] this option is turned on with the SETLOCAL EnableDelayedExpansion command.

Variable expansion means replacing a variable (e.g. %windir%) with its value C:\WINDOWS

  • By default expansion will happen just once, before each line is executed.
    In this case, a "line" includes bracketed expressions even if they are formatted to run over several lines.

  • When !delayed! expansion is turned on, the variables will be expanded each time the line is executed, or for each loop in a FOR looping command.

And here's a decent example from that page showing how it works in practice.

@echo off
SETLOCAL EnableDelayedExpansion
Set "_var=first"
Set "_var=second" & Echo %_var% !_var!

The output is

first second

Note that the above example has to be in a batch file to work properly.

Anyhow, it's an insane rabbit hole that I didn't schedule for this sprint and fell into anyhow. I'm going to cut my losses now that I've warned others who may have been as naïve about batch scripts as I was, oh so long (less than three weeks 😉) ago.

Oh good heavens. I guess this makes sense. The first example can be taken by itself as a brain teaser, I think.

From, again on EnableDelayedExpansion:

@echo off
Set _html=Hello^>World
Echo %_html%

In the above, the Echo command will create a text file called 'world' - not quite what we wanted!

Got that figured out? If not, sit and think about it for a second before reading more.

Got it? Good. Clever, huh?

This is because the variable is expanded at parse time, so the last line is executing Echo Hello > World and the > character is interpreted as a redirection operator.

If we now try the same thing with EnableDelayedExpansion:

Setlocal EnableDelayedExpansion
Set _html=Hello^>World
Echo !_html!

With delayed expansion, the variable (including the > ) is only expanded at execution time so the > character is never interpreted as a redirection operator.

This makes it possible to work with HTML and XML formatted strings in a variable.

Labels: , , ,

posted by ruffin at 1/11/2023 12:27:00 PM
Friday, December 23, 2022

Ah, one quick Markdown lesson I learned recently.

If you've got Markdown inside of "real" block-level html tags, it's not supposed to be rendered.

<div>This is **some Markdown**.</div>

That shouldn't be bold. It renders as...

This is **some Markdown**.

Unexpected, but (with a hat tip to this SO answer), apparently correct behavior according to The Grubes:

The only restrictions are that block-level HTML elements — e.g. <div>, <table>, <pre>, <p>, etc. — must be separated from surrounding content by blank lines, and the start and end tags of the block should not be indented with tabs or spaces. Markdown is smart enough not to add extra (unwanted) <p> tags around HTML block-level tags.


Note that Markdown formatting syntax is not processed within block-level HTML tags. E.g., you can’t use Markdown-style emphasis inside an HTML block.

Welcome to the land of unintended consequences. It looks like the deal was "stop adding all these danged <p> tags around my inline block-level html; it'd duping the breaks," and we got, "stop ALL markdown rendering in block tags" instead.

There is, however, a non-standard workaround, as described by Python-Markdown's docs:

The markdown attribute can be assigned one of three values: "1", "block", or "span".

Note: The expressions “block-level” and “span-level” as used in this document refer to an element’s designation according to the HTML specification. Whereas the "span" and "block" values assigned to the markdown attribute refer to the Markdown parser’s behavior.


When the markdown attribute is set to "1", then the parser will use the default behavior for that specific tag.

So if you have

<div markdown="1">This is **some Markdown**.</div>

you should have

This is some Markdown.

... but I need to add that to MarkUpDown if I want to support it. And since it's not like someone is going to add that that doesn't want it, I guess I should!

Labels: , , ,

posted by ruffin at 12/23/2022 08:36:00 AM
Thursday, December 22, 2022

The longer I work in Windows, the more I find myself using cmd.exe. I use PowerShell plenty too, but if I want something quick that can run anywhere [someone's on Windows], I use a batch file, and over the years, kinda like VIm, I've slowly become if not proficient then at least competent.

Nearly (and maybe even over at this point) thirty years ago I had a guy wisely tell me, when I was considering buying a new Mac or Windows PC, "It's all zeros and ones." Same for script languages, mostly. It might be a pain to learn batch scripting on Windows sometimes, but there's very little you can't do if you set your mind to it.

But this is a really neat trick to create "arrays" in batch that I've never seen. I've edited a bit to allow running in a .bat cleanly:


echo off

set Arr[0]=apple
set Arr[1]=banana
set Arr[2]=cherry
set Arr[3]=donut

set "x=0"

if defined Arr[%x%] (
    call echo %%Arr[%x%]%%
    set /a "x+=1"
    GOTO :SymLoop


While I'm at it, here's a PowerShell script I've been using to approximate grep there. I've dabbled in this problem before, but it's usually a good idea to reduce it to script instead of leaving only human-readable lessons learned:

param (

if ($optionalFileForOutput) {
    Get-ChildItem -Path $filepathToSearch |Select-String -Pattern $needle |Out-File -width 99999 $optionalFileForOutput
} else {
    select-string -path $filepathToSearch -pattern $needle

That captures the Out-File wackiness from the previous post but also wraps Select-Stringing a file, making it easier to remember, not that it's difficult. I should fix the casing.

Labels: , , , ,

posted by ruffin at 12/22/2022 10:21:00 AM

Support freedom
All posts can be accessed here:

Just the last year o' posts:

URLs I want to remember:
* Atari 2600 programming on your Mac
* joel on software (tip pt)
* Professional links: resume, github, paltry StackOverflow * Regular Expression Introduction (copy)
* The hex editor whose name I forget
* JSONLint to pretty-ify JSON
* Using CommonDialog in VB 6 * Free zip utils
* git repo mapped drive setup * Regex Tester
* Read the bits about the zone * Find column in sql server db by name
* Giant ASCII Textifier in Stick Figures (in Ivrit) * Quick intro to Javascript
* Don't [over-]sweat "micro-optimization" * Parsing str's in VB6
* .ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); (src) * Break on a Lenovo T430: Fn+Alt+B
email if ya gotta, RSS if ya wanna RSS, (?_?), ¢, & ? if you're keypadless

Powered by Blogger Curmudgeon Gamer badge
The postings on this site are [usually] my own and do not necessarily reflect the views of any employer, past or present, or other entity.