Wednesday, July 22, 2015

Regex: Match All or Nothing

I ran across a fun Regular Expression problem today. This uses the PCRE features such as \K, and verbs like (*SKIP) and (*FAIL).

A string contains text which has some amount of whitespace at the beginning of each line (except the first line, which we want to ignore). Capture that leading whitespace, so it can be counted, replaced, etc.

The only caveat: if ANY of the lines (barring the first line) have no leading whitespace, then NONE of the lines with leading whitespace should be captured.

function some_name(
    1,
    20,
    'this is text',
    );

Other than the top line that starts with 'function some_name', the four lines that start with whitespace should match with an expression such as this:

^(\s+)

But how do you handle this scenario:

function some_name(
    1,
20,
    'this is text',
    );

where the line with '20,' has no leading whitespace?

The solution:

^\A[^\n]*\n\K
(?=[\s\S]*^\S)
(?:[\s\S]*(*SKIP)(*FAIL))
|
^(\s+)

Let's break this down:

^\A[^\n]*\n\K

The \A starts at the beginning of the string. Since we don't care about the first line, [^\n]*\n matches everything to the end of the line (including the newline). Then the \K option tells the regex engine to throw away all the stuff it matched up to this point and start anew.

Then using a look-ahead, see whether ANY lines start with a non-whitespace character:

(?=[\a\S]*^\S)

If it found even a single line that matches this, consume ALL the characters to the end of the entire string, then (*SKIP) them and mark the match as a (*FAIL).

What this effectively does is consumes all the remaining characters, so any other portion of the regex pattern that comes afterward has no text to match against, and the pattern FAILS.

On the other hand, if that look-ahead could NOT find any lines that start with non-whitespace, the OR condition at the end will look for, and match, the leading whitespace:

^(\s+)

You can see this in action on this page: https://regex101.com/r/pC6gB3/1 where you can see the differences when you add or remove the leading whitespace for any of the lines (again, barring the first line, which we ignore).

Saturday, January 4, 2014

Hiding the Minecraft Dock icons in OS X

When you launch Minecraft, you get two icons in your Dock. One icon comes from the preloader/launcher, and it is tied to the /Applications/Minecraft.app application that you double-click to play the game. The second icon is embedded within the internals of Minecraft itself, found within your ~/Library/Application Support/minecraft folder.

Changing the former icon is relatively straightforward.

  1. Find any application whose icon you wish to borrow
  2. Right-click the file and select "Show Package Contents"
  3. Navigate to Contents/Resources/favicon.icns
  4. View the file's info (either right-click and choose "Get Info" or hit Command-I on the keyboard)
  5. Select the icon at the very top-left corner of the Info window, and it highlights slightly.
  6. Copy it with Command-C or Edit->Copy. This copies the icon's picture, not the file itself.
  7. Now, navigate to /Applications/Minecraft.app and repeat steps 2-5. You should have the Minecraft icon highlighted.
  8. Paste with Command-V (or Edit->Paste)
This changes the preloader/launcher icon so any time you open it, a different icon than the standard Minecraft icon is shown.

As an aside, you could also hide this Dock icon entirely when it is running. To do this, locate the Info.plist file at:

  /Applications/Minecraft.app/Contents/Info.plist

Add this line somewhere about halfway down:

   <key>LSUIElement</key><string>1</string>

This hides the Dock icon when the application is running.


The second Minecraft icon is embedded within your personal ~/Library folder. The file on my system is located at:

  ~/Library/Application Support/minecraft/assets/objects/99/991b421dfd401f115241601b2b373140a8d78572

This 991b421...572 file has no extension and appears to have a nondescript icon when viewed in Finder. However, if you select the file in Finder and hit Space to show a larger preview, it expands to show a picture of the Minecraft icon.

This file is actually named according to its SHA1 digest: Its file size is exactly 114,786 bytes, and it is in the '/objects/99/' directory because the first two digits of its name start with '99'. (Knowing all this is important for the next steps.)

This 991b..8572 number is referenced within a JSON file located in:

   ~/Library/Application Support/minecraft/assets/indexes/

At the time of this writing, 1.7.4 is out, so the file is called

   ~/Library/Application Support/minecraft/assets/indexes/1.7.4.json

A few entries down, you will see the lines:


    "icons/minecraft.icns": {
      "hash": "991b421dfd401f115241601b2b373140a8d78572",
      "size": 114786
    },


The hash, as well as the file size, match that file exactly.

To replace this icon with another of your choosing, find an application's icon file (use the same steps 1-4 above). Once you locate the .icns file, you need to get its SHA1 digest (I use 'sha1deep' from the MacPorts package 'md5deep', but you can use anything that produces it). You will also want to note its exact file size, in bytes, to replace the information above.

I used the icon from Google Chrome, located at

  /Applications/Google Chrome.app/Contents/Resources/app.icns

which has a digest of

  dcba7f4d611c5b0ea02ef583284ed211f6b5c757

and a filesize of 159,992 bytes. I copied Google Chrome's app.icns file into:


   ~/Library/Application Support/minecraft/assets/objects/dc/dcba7f4d611c5b0ea02ef583284ed211f6b5c757


Notice that the path is '/objects/dc/' instead of '/objects/99/' to match the first 2 characters of the filename.

In the 1.7.4.json file, I replaced the entry above with:


    "icons/minecraft.icns": {
      "hash": "dcba7f4d611c5b0ea02ef583284ed211f6b5c757",
      "size": 159992
    }

I need to point out that the next time you run Minecraft, this JSON file will get overwritten. You will need to change the file permissions to read-only so Minecraft is not able to revert your changes back to the original 991...572 filename.

To make this file read-only, I opened Terminal (/Applications/Utilities/Terminal.app), navigated to the 

   ~/Library/Application Support/minecraft/assets/indexes/

path, and used this command:

   chmod 444 1.7.4.json

This removes the ability for Minecraft to update this file (it'll probably come back to bite me after the 1.7.5 and later revisions, but it works for now), and points the second, harder-to-modify Minecraft icon to the new icon (Google Chrome) instead.


Whew!

That was longer than I anticipated, but I hope it was written simply enough that anyone can understand it. Feel free to leave questions and comments.