/dev/trouble
Eric Roller's Development Blog

General

General development topics.

In my setup, fastlane usually runs on a separate computer in my closet. Therefore I find it helpful to add audio messages such that I can hear the status:

sh 'say "Creating screenshots for iOS devices."'

Fastlane's say() command sometimes hasn't worked for me. That's why I have used sh 'say'. However, that was recorded by fastlane as an action. Using this Ruby syntax is not tracked as an action:

`say "Processing images."`

But neither does its produce any text in the log. So I created this Ruby function which prints the message and then speaks it:

def talk(s) puts s; `say "#{s}"` end

With the function anywhere in fastlanes Fastfile, one can just call it like a command:

talk "Framing screenshots"

Speaking Multiple Phrases

This all works nicely, but I discovered that my error message needs to be made more prominent. I might be in the bathroom and don't hear the announcement. That's why I have now changed it to be a lot longer and a lot more annoying.

error do |lane, exception|
  phrases = [
      "Are you serious?",
      "Huston, we've had a problem!",
      "I can't believe it!",
      "I thought you had fixed that.",
      "Is it broken again?",
      "Maybe we can just reboot it?",
      "No! No! No, not again!",
      "Oops! I did it again!",
      "Something smells fishy!",
      "There is a left-over screw here.",
      "There must be a rational explanation for this.",
      "This must be a mistake!",
      "What if we pressed the red button?",
      "What is going on?",
      "What is this switch for?",
      "Where's the beef?",
      "Where is the user manual?",
      "Who pressed the red button?",
      "Who let the dogs out?",
      "You must be joking!",
  ]

  `say "#{ phrases.shuffle.join(" [[slnc 2500]] ") }"`
end

Note that the phrases are shuffled and concatenated by [[sinc 2500]] instructions which add 2.5-second-long pauses between the phrases. I found that in Apple's Speech Synthesis Programming Guide.

Whenever I like to show or hide view elements, I stumble over the hidden() view property:

struct MyView: View {
    var body: some View {
        Text("Visible")

        Text("Not visible")
            .hidden()
    }
}

What perplexes me is why anyone would need this? If it is always hidden, then why add it in the first place??

Well, I guess it could be used to create some well-defined, empty space in the view layout.

Now, this is what I would really have a use for:

struct MyView: View {
    var show_info: Bool

    var body: some View {
        Text("Visible")

        Text("Sometimes visible")
            .hide(!show_info)    // OBS! Doesn't exist!
    }
}

Depending on whether or not the hidden part should be used when laying out the view, a different approach is needed. Either without taking any space:

struct MyView: View {
    var show_info: Bool

    var body: some View {
        Text("Visible")

        if show_info {
            // Not occupying space when hidden
            Text("Sometimes visible")
        }
    }
}

Or occupying space:

struct MyView: View {
    var show_info: Bool

    var body: some View {
        Text("Visible")

        // Taking space, even if transparent
        Text("Sometimes visible")
            .opacity(show_info ? 1.0 : 0.0)
    }
}

When the NumberFormatter() is asked to output Decimal/NSDecimalNumber values with more than 15 fractional digits, the output is rounded (apparently to the precision of Double numbers). Example:

14 decimals: 3.14159265358979
15 decimals: 3.141592653589793
16 decimals: 3.1415926535897900
17 decimals: 3.14159265358979000
18 decimals: 3.141592653589790000
19 decimals: 3.1415926535897900000
20 decimals: 3.14159265358979000000

Note the trailing zeros which are not expected.

Note that Decimal numbers support a precision of 38-decimals. Example: Decimal.pi.description

3.14159265358979323846264338327950288419

Code to reproduce the rounding problem:

import Foundation

var number = Decimal.pi
let formatter = NumberFormatter()

for width in 10 ... 38 {
    formatter.maximumFractionDigits = width
    formatter.minimumFractionDigits = width
    print(String(width) + " decimals: " +
            (formatter.string(from: number as NSDecimalNumber) ?? ""))
}

Apple feedback ID: FB9835108

The calculation for 256^8 using Doubles works as expected:

(lldb) po pow(Double(256), Double(8))
1.8446744073709552e+19

However, trying to convert the result into a Decimal crashes:

(lldb) po Decimal(pow(Double(256), Double(8)))
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been returned to the state before expression evaluation.

Decimals should handle values with up to 38 digits as mantissa with exponents from –128 through 127.

Apple feedback id: FB7706665

Similar to what I have been doing with subversion, I now also create backups for my git repositories.

Note that the git repositories on the backup host are local repositories (not cloned ones) and that they are named ".git".

From /etc/periodic/monthly/mine.monthly:

if [ -d /Library/Repositories ]; then
  cd /Library/Repositories
  for i in *.git ; do
    # Not a local git repository? Ignore it.
    if [ ! -f "$i/HEAD" ]; then
        continue
    fi
    line="/tmp/monthly-$i.out"
    # File: /Volumes/Backups/Attic/git-file-2016-08-10.bundle
    name="git-`basename $i`-`date +%F`.bundle"
    dump="/Volumes/Backups/Attic/$name"
    if [ ! -f "$dump" ]; then
      echo "Bundling git repository: $name"
      { cd "$i"; git bundle create "$dump" --all; } >& "$line"
      # Change the user for the dump file.
      chown gollum:gollum "$dump"
      # Only output the last stderr line then delete the file.
      tail -1 "$line"
      rm "$line"
    else
      echo "Warning: Dump already exists: $name"
    fi
  done
else
  echo "Error: Repositories directory not found."
fi

The weekly backups are much the same, except that only the three most recent repositories are backed up. Thus, the for loop in /etc/periodic/weekly/mine.weekly changes to:

  for i in `ls -1t *.git | sed '3 q'` ; do
    [...]
    line="/tmp/weekly-$i.out"
    [...]

N.B. I still use TrashLater to keep files for only 6 months in the Attic folder.

Blog Reboot: First Post

- Posted in General by

I have decided to switch my blog to a different backend; one that is easier to maintain and doesn't rely on MySQL (which I always found overkill and difficult to backup). Now I only need to extract my old blog posts from that last database dump file...

Update: Having worked through the dump file in a text editor, I found hundreds of duplicate entries marked as 'inherit' and 'nnn-revision-m'. These entries may be the result of multiple edits of a post. This is exactly the kind of data bloat that I strive to avoid. The current solution stores all entries in simple text files. Easy to back up. Easy to index. No duplication.

Just added a new section to /etc/periodic/monthly/mine.monthly in the attempt to create monthly backups of my subversion repositories, using compressed dump files:

[Update: Now using quoted variable names.] [Update: Now checking "format" file.]

if [ -d /Library/Repositories ]; then
  cd /Library/Repositories
  for i in * ; do
    # Not a subversion repository? Ignore it.
    if [ ! -f "$i/format" ]; then
        continue
    fi
    line="/tmp/monthly-$i.out"
    # File: /Volumes/Backups/Attic/svn-file-2014-01-17.dump.gz
    name="svn-$i-`date +%F`.dump.gz"
    dump="/Volumes/Backups/Attic/$name"
    if [ ! -f "$dump" ]; then
      echo "Dumping subversion repository: $name"
      # Dump into a gzipped file; "Dumped revision n" into temp file.
      { /usr/bin/svnadmin dump "$i" | gzip -c -9 > "$dump"; } >& "$line"
      # Change the user for the dump file.
      chown gollum:gollum "$dump"
      # Only output the last stderr line then delete the file.
      tail -1 "$line"
      rm "$line"
    else
      echo "Warning: Dump already exists: $name"
    fi
  done
else
  echo "Error: Repositories directory not found."
fi

N.B. I also use TrashLater to keep files for only 6 months in the Attic folder.

For /etc/periodic/weekly/mine.weekly I only dump the most recently modified repository since I don't normally work on multiple projects. This is done with a different foreach loop, where we get just one repository file name:

if [ -d /Library/Repositories ]; then
  cd /Library/Repositories
  # List the dir by last modified and get the first entry only.
  for i in `ls -1t | sed '1 q'` ; do
    line="/tmp/weekly-$i.out"
    # [...as above...]
  done
else
  echo "Error: Subversion repositories not found."
fi

phpMyAdmin

- Posted in General by

I see from my web-server log that people like to guess the path to phpMyAdmin in all different variations, colours, shapes and sizes. Now, this is exactly why I have not installed it in a fashion that makes it visible to anyone other than me. Better yet, come to think of it, I have not installed it at all and I don't think I ever will. But if I do, you should probably search for all variations, colours, shapes and sizes of phpMyNeatlyStashedAwayAndHiddenInstallationOfMyAdmin.

While it is convenient to add applications to the Login Items* list, it is also annoying when it takes ages for all of them to launch before you can do any meaningful work.

(*) In the Settings applications, select Accounts, your account and then the Login Items tab.

It occurred to me that one or the other application does not need to be available at login, but it would be nice to load it later. For instance, I don't use Skype during the day, but more in the evenings to contact my friends. So how about launching Skype at 5 p.m.?

The following launchd specification will do exactly that: launch Skype at 5 p.m. (or when not possible, whenever you log in next time):





        Label
        com.skype.skype
        ProgramArguments
        
                /usr/bin/open
                /Applications/Skype.app
        
        StartCalendarInterval
        
                Minute
                0
                Hour
                17
        


Save this file as ~/Library/LaunchAgents/com.skype.skype.plist (note that the Label must correspond to the file name) and wait for magic to happen when you log in next time...

After having spent half an hour looking for a drawing program for vector graphics on the Mac, it occurred to me that I already had one: Keynote .

Obviously, it is aimed at creating stunning presentations, but there is no reason why it cannot be used for any other kind of drawing. When finished, a drawing can be exported as an image file (JPEG, PNG, or TIFF) or simply grabbed from the screen with a screen shot (just use shift-cmd-4 and draw a rectangle).

Keynote is bundles with all new Macs but it is also available separately from the Mac App Store ($20). This price compares well to other graphics applications like OmniGraffle (standard $100, pro $200).