Kevins Guide To Using Dockerfile

Kevin’s Guide to Using Dockerfile and docker-compose.yml

and generaly, how to docker-enable an application.

As a rule, don’t install libraries or scripting languages directly onto your host. e.g. The moment you install Ruby 1.9.1 onto your host, you’ll run into a must-have app which requires Ruby 1.8.5. Yes, there are ways to manage multiple Ruby (or multiple Python or multiple libc), but it is just safer and cleaner to put all that stuff inside a container.

A Dockerfile specifies how to build an image. It doesn’t address how to run that image as a container. docker-compose is a tool to run your images. Here’s how I use something new under Docker on my Mac.

Strictly speaking, docker-compose is about running multiple containers. However, you can use it to run a single container, and it makes the command line for that container simpler, by allowing you to put some of your options in the docker-compose file.

Note: This isn’t quite how I set up my Jekyll images. Those were done before I defined my canonical form.

  1. Create a folder for your app. We’ll refer to it as app-dir. Put everything shown below into this folder.

  2. Write the Dockerfile just to build the image, not to run it. e.g.

    # Use the specified image as a baseline.
    # See https://hub.docker.com/explore/ for official images.
    # Format is one of:
    #   FROM image
    #   FROM image:tag
    #   FROM image
    # The one below comes directly from the doc at https://hub.docker.com/_/node/
    FROM node:alpine
    
    # The RUN command says, "After you download the image, run this command to
    # customize the image to your needs.  Runs as part of building (not starting) the image
    #
    # The RUN below says:
    #   Install v 5.1.15 of Tiddlywiki (TW) into Node.js using npm (the Node package mgr).  #   You could specify a newer version (or no version, by omitting '@5.1.15') but you 
    #  may have to adapt these instructions if TW has major changes.
    # I recommend that you ALWAYS specify a version or a tag.
    RUN npm install -g tiddlywiki@5.1.15
    RUN /usr/local/bin/node /usr/local/bin/tiddlywiki wiki-data --init server
    
    # Define a volume for mounting on host file system.  (i.e. Enable mounting this path.)
    VOLUME /var/lib/tiddlywiki
    
    # set the container's working directory for any 
    # RUN, CMD, ENTRYPOINT, COPY and ADD commands *below*.
    # Reminder: TW writes its files to the working directory.
    WORKDIR /var/lib/tiddlywiki
    
    # Add a script which you will (later) launch inside the container to do tasks
    # you want done EACH time the container starts.
    ADD container-rc.sh /usr/local/bin/container-rc.sh
    
    # Expose any ports.  Note that on Win/Mac, Docker runs in a VM and this is the
    # VM port.  You need to map the VM port to a host port at runtime, if you are to
    # access it.
    EXPOSE 8080
    

  3. Write docker-compose.yml to specify how to run the image. (Reminder: No tabs in YAML.)

    version: "3"                    # Using version 3 docker-compose file format.
    services:
        tw-node:                    # Name of the image? service? I'm creating.
            build: .                # Use the Dockerfile in . to build the image
            ports:
                - "127.0.0.1:8080:8080"
                                    # host:container.  Quotes strongly recommended.
                                    # Two forms: hostPort:containerPort or 
                                    #     hostIP:hostPort:containerPort
            volumes:
                                    # /host/path:/container/path
                - /Users/kevin/Sync/Sites/tw-node:/var/lib/tiddlywiki
            command: /bin/sh /usr/local/bin/container-rc.sh
    
  4. Create runme.sh, to launch the container, containing

    #!/bin/bash
    cd /path/to/app-dir
    /usr/local/bin/docker-compose up
    
  5. Create container-rc.sh, which starts the processes inside the container:

    ??
    
  6. Be sure to:

    chmod +x runme.sh
    chmod +x container-rc.sh
    

  7. Build your image with:

    docker-compose build --no-cache
    
    • You can usually omit –no-cache, and it will use cached layers upon which this image depends. Adding –no-cache ensures that you don’t get a layer you cached which was built with special args. I don’t do a lot of building, so I don’t mind if it is slow.
  8. Test your image with:

    ./runme.sh
    
  9. It can take 15-20 seconds to start a simple image. You’ll usually see this when it is up: “Server running… press ctrl-c to stop.”

  10. Once you validate your service is working, press control-C to stop it.

  11. Create a LaunchCtl file /Users/kevin/Library/LaunchAgents/com.kleinfelter.SERVICE_NAME_HERE.plist to specify how to run your container as a service:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.kleinfelter.SERVICE_NAME_HERE</string>
        <key>ProgramArguments</key>
        <array>
            <string>/bin/bash</string>
            <string>/path/to/app-dir/runme.sh</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>KeepAlive</key>
        <true/>
        <key>StandardOutPath</key>
        <string>/var/log/SERVICE_NAME_HERE/SERVICE_NAME_HERE.log</string>
        <key>StandardErrorPath</key>
        <string>/var/log/SERVICE_NAME_HERE/SERVICE_NAME_HERE.err</string>
    </dict>
    </plist>
    
  12. sudo mkdir /var/log/SERVICE_NAME_HERE
    sudo chown kevin /var/log/SERVICE_NAME_HERE
    
  13. Then load your LaunchCtl with:

    launchctl load /Users/kevin/Library/LaunchAgents/com.kleinfelter.SERVICE_NAME_HERE.plist
    
  14. And check to be sure your service is working.

  15. Reminder: To open a bash prompt in your image (without installing ssh and using ssh):

    docker docker ps --format 'table ' #Identify the desired container name
    docker exec -it CONTAINER_NAME_HERE /bin/bash #-i gets STDIN, -t gets a pseudo-tty
    
  16. Rejoice!

Outlook 2016 Sending Empty Emails

My Outlook started sending empty emails. The really annoying thing was that I could spend an hour composing the perfect email, hit Send, and it would strip the content before sending. The email in the Sent folder was devoid of content too. Gone!

I dunno if the following really fixed it, or if it simply went away on its own at the same time:

  • File > Options > Mail > Compose messages in this format:
    • Set it to Rich Text
  • Send an email. It works!
  • Set format back ot HTML. It still works!

Leaving LastPass

I’m leaving LastPass. I was uncomfortable when LogMeIn bought them. Then it started flaking out. Little things, like crashing after login; the Mac version saying it was installing the binary shim but not actually doing it.

The final straw was when it started telling me that it couldn’t contact the server. The work-around included clearing the cache. When I started to clear the cache, LastPass warned me that doing so could lose data because of a ‘retry’ file. I contacted support, explained that I considered data loss unacceptable, and they said I should remove/reinstall the browser plugin. I did so. Whoops. Removing the browser plugin… removes the plugin data, including the retry file. So I lost some updates and I have no way to know what they were.

Naturally this happened less than a week after I had renewed my LastPass subscription!

There are only two things a password wallet must do: Keep my passwords confidential and give them to me when I ask. Losing data causes the ‘give to me when I ask’ to fail.

The trouble with all services is that you’re trusting them not to have bugs (or a deliberate, hidden leak).

I need:

  • Mac
  • Windows
  • Sync between a Mac and a Windows computer.
  • Local storage
  • Export and import of passwords

I want:

  • Not cloud. If online, it must be zero-knowledge and 2FA.
  • Audited security
  • Sync between multiple clients. (I could live with entry on just one device.)
  • Read access on Android.
  • High confidence in confidentiality and data integrity.
  • To avoid installing eleventy-three ‘extensions’ which don’t get audited as well as the main product.
  • YubiKey or other 2FA. (Key file on USB would be OK.)
  • Ability to turn OFF auto-update.
  • Some way to export passwords to my wife, and to read her passwords.

Some possibilities:

  • KeePass Family
    • Notes:
      • Import from LastPass: https://tibdex.github.io/lastpass-to-keepass/
      • If implemented right, a web data file with a local key file, should make it decryptable only by me.
      • It is really a database, without browser integration. To use it:
        • Navigate your browser to the login page
        • Bring up KeePass
        • Search KeePass for the right login record.
        • Press the auto-type keystroke. It will minimize itself and type ID, tab, password, Enter.
    • KeeWeb - cross platform. In browser, or as Electron app. Desktop and cell phone.
    • KeePass
      • Requires Mono on Linux and Mac. Reportedly has issues on Mac.
    • KeePassXC - cross platform. (KeePassX is obsolete.)
  • Bitwarden, self-hosted - (via Docker, or build from source)
  • Google Smart Lock - Google Chrome built-in password manager. It is ‘in the cloud’.

Improbable, but possible:

  • Text file - Let’s not reject this out of hand.
    • I do encrypt my disk drives. But if the data gets onto my work PC, my employer could read it - even if I use EFS.
    • I could use VeraCrypt portably on my work PC.
    • A text file, plus a shim to encrypt/unencrypt the current file open in my editor. (Risk of forgetting to encrypt before save!)
  • Sync a modern Excel file encrypted with SHA-512.
  • An Android-only Solution
    • I could store them solely on my cell phone (with a backup somewhere).
    • I’d want to be able to fingerprint or face-recognize to unlock the phone and the safe.
  • Keeper - multi-platform. Proprietary. Maybe a better LastPass, but similar in essence.
  • Text file in Linux VM, on a USB drive, with encrypted file system.

Rejected:

  • LastPass - nope. It lost some data.
  • Password Safe - the Mac version in unofficial. I don’t want ‘unofficial’ with my passwords unless it is 3rd-party audited.
  • SplashID - Looks interesting. It puts the program and the data on a USB stick. 42% 1-star (awful) ratings on Amazon!

Using AppleScript for GUI Scripting - Discovering the Interface

Sometimes you want to script a Mac OS X app, and you can’t really do it via the dictionary. At that point, you revert to something called “GUI scripting.” You automate the app’s user interface via the Accessibility interface. This is similar to what Autohotkey users do for Windows PCs.

Often, you want to do something like “Click on the Windows menu; then click on the first item.” The trick is that different apps implement menus which LOOK the same with different structure. You need to discover the actual structure of the menu in order to click it programmatically.

Here is a sample script to show all of the menu items on an application (Google Chrome) menu bar.

`osascript -sso > /private/tmp/StatusBarItems <&- <<EOF
tell application "System Events"
    get properties of every  menu bar item of menu bar 1  of process "Google Chrome"
end tell
EOF`

This will write the menu bar structure to /tmp/StatusBarItems. Buried in that file, you’ll see the following snippet. (The whole output is much larger because it dumps EVERY item in the menu.)

{minimum value:missing value, orientation:missing value, position:{470, 0}, class:menu bar item, accessibility description:missing value, role description:"menu bar item", focused:missing value, title:"Window", size:{70, 22}, help:missing value, entire contents:{}, enabled:true, maximum value:missing value, role:"AXMenuBarItem", value:missing value, subrole:missing value, selected:false, name:"Window", description:"menu bar item"}

Reformatting that, and dropping all of the “missing value” entries, it looks like:

{
    position:{470, 0}, 
		class:menu bar item,
		role description:"menu bar item",
		title:"Window", 
		size:{70, 22},
		entire contents:{}, 
		enabled:true,
		role:"AXMenuBarItem",
		selected:false,
		name:"Window", 
		description:"menu bar item"
}

The upshot of that is that you can use code to look for a “menu bar item” (in menu bar 1) where name = “Window”. You could probably search for the other attributes, but ‘menu item with name = x’ makes for more readable code than ‘menu item at 470,0.

The items under the Window menu have their own structure. Dump that structure with this code:

osascript -sso > /private/tmp/StatusBarItems <&- <<EOF
tell application "System Events"
    get properties of (first menu bar item where name is "Window") of menu bar 1  of process "Google Chrome"
end tell
EOF

That dumps the following to /tmp/StatusBarItems:

{minimum value:missing value, orientation:missing value, position:{470, 0}, class:menu bar item, accessibility description:missing value, role description:"menu bar item", focused:missing value, title:"Window", size:{70, 22}, help:missing value, entire contents:{}, enabled:true, maximum value:missing value, role:"AXMenuBarItem", value:missing value, subrole:missing value, selected:false, name:"Window", description:"menu bar item"}

and when you reformat and strip out missing values, it looks like:

{
    position:{470, 0}, 
		class:menu bar item,
		role description:"menu bar item",
		title:"Window", 
		size:{70, 22},
		entire contents:{}, 
		enabled:true,
		role:"AXMenuBarItem",
		selected:false,
		name:"Window", 
		description:"menu bar item"
}

which looks a whole lot like the output from the first dump. (In the first dump, which dumped everything in the menu, we visually located this menu item and manually extracted it. In this second dump, we used code to extract that one menu item.)

This information tells you that Google Chrome has:

  • A menu bar 1 containing a menu bar item named “Window”

You could dump the content of the Window menu bar item, and see the things that are in it. I wanted to click on the “Pin Tab” item in the Window menu. I happen to know that entries in a menu which don’t launch sub-menus are called “menu item”, so I didn’t take the time to write osascript to dump that menu.

Putting all of this information together, I knew I could write code like the following:

	tell application "Google Chrome" to activate
	tell window 1 of application "Google Chrome" to set visible to true
	tell application "System Events"
		tell process "Google Chrome"
			set m1 to (first menu bar item where name is "Window") of menu bar 1
			set mx to menu item "Pin Tab" of menu 1 of m1
			click mx
		end tell
	end tell

Remove OneDrive From Office 2016 Save Menu

Office 2016 always offers the option of saving your file to OneDrive (via File/Save or File/Save As). I hate that, because I don’t use OneDrive. To disable, set:

HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Common\SignIn\SignInOptions = DWORD 3

Whooshing From Chrome to Safari and Back

Chrome works better for me than Safari, particularly when I have many, many tabs open. However, when I run my Macbook on batteries, Chrome drains batteries too fast, so I’d like to use Safari when I’m on battery power. I created a couple of Applescripts to move my open tabs from Chrome to Safari and vice versa.

I found scripts online to do these, but they appear to rely on obsolete editions of OS X. My scripts below work on OS X High Sierra with the current Google Chrome as of 2018.01.01.

Conceptually, it is a simple task: Take these tabs in one browser and open them in the other. (“Whoosh them” from one browser to the other.) There are some challenges along the way:

  • The object model (“dictionary” in Apple parlance) for the browsers differ.
  • The object model is incompletely documented. There are many properties and methods which you just have to discover via Googling.
  • Pinned tabs behave differently between Chrome and Safai. Frankly, the Safari practice of putting your pinned tabs in every Safari window is just stupid.
  • You can’t pin a tab via the object model – you must resort to GUI scripting.
  • Bringing a window or tab to the foreground changes the order of the browser’s windows/tabs, so you can’t iterate through windows/tabs while bringing them to the foreground. You have to collect the IDs and then iterate through the IDs.
  • AppleScript is just an all-around peculiar language and environment.

Here is Chrome-to-Safari:

-- Move all tabs from Chrome to Safari.


-- Optionally close existing Safari tabs first
set question to display dialog "Close exisisting Safari tabs first?" buttons {"Yes", "No"} default button 1
set answer to button returned of question
if answer is equal to "Yes" then
	closeAllSafariTabs()
end if

tell application "Google Chrome"
	
	-- for every Chrome window, open a Safari Window
	repeat with theChromeWindow in windows
		set theSafariWindow to my openNewSafariWindow()
		
		-- for every tab in the Chrome window, open a tab in the Safari window
		repeat with theChromeTab in theChromeWindow's tabs
			my makeSafariTab(theSafariWindow, URL of theChromeTab)
		end repeat
	end repeat
end tell

pinSpecialTabsInSafari()

tell me to activate
set question to display dialog "Close old Chrome tabs?" buttons {"Yes", "No"} default button 2
set answer to button returned of question
if answer is equal to "Yes" then
	closeAllChromeTabs()
end if

tell application "Safari" to activate


-- Open a *new* Safari window.
on openNewSafariWindow()
	tell application "Safari"
		make new document at end of documents
		set theSafariWindow to window 1
	end tell
	return theSafariWindow
end openNewSafariWindow



-- I have a set of sites for which I used pinned tabs in Chrome.
-- Note that because pinning behaves differently in Chrome and Safari, I don't always pin my pin-desired tabs when I move them to Safari.
on pinSpecialTabsInSafari()
	
	-- Note: You can't simply iterate windows and tabs because activating a window/tab re-orders the collection.
	--       You have to get a collection of IDs and iterate the IDs because they don't change.
	
	tell application "Safari" to set windowIds to (id of every window)
	tell application "Safari" to activate -- have to activate Safari because I'm automating clicks on its menus.
	
	repeat with wid in windowIds
		tell application "Safari"
			set theSafariWindow to (first window whose id = wid)
			set index of theSafariWindow to 1 -- bring THIS Safari window to top of Safari.
			
			tell theSafariWindow
				-- if you don't do this, sometimes Safari is the front app, with the desired window in the foreground, but it isn't actually activated.
				set visible to false
				set visible to true
			end tell
			
			repeat with t in every tab of theSafariWindow
				tell theSafariWindow to set current tab to t
				my waitForFrontTabToLoad()
				set theURL to URL of t
				if my shouldPinMe(theURL) then
					my pinActiveTab()
				end if
			end repeat
		end tell
	end repeat
	
	
end pinSpecialTabsInSafari


-- Create a new Safari tab in the specified window.
on makeSafariTab(theSafariWindow, theURL)
	
	if (theURL as string) = "chrome://newtab/" then
		-- skip empty tabs.  (You'd need to change chrome://newtab/ to about:blank in order to open it in Safari.)
	else
		tell application "Safari"
			try
				tell window 1 to set current tab to make new tab at end of tabs of theSafariWindow with properties {URL:theURL}
			on error
				open location theURL
			end try
			
			-- Close empty 'Favorites' tab created when making new window.
			set thisTab to tab 1 of theSafariWindow
			if thisTab's name as string = "Favorites" then close tab 1 of window 1
			
		end tell
	end if
end makeSafariTab


-- Wait for the front-most tab of Safari to finish loading (at least until it has a title).
on waitForFrontTabToLoad()
	repeat until leftString((name of front document of application "Safari"), 8) is not "Untitled"
		delay 0.1
	end repeat
end waitForFrontTabToLoad


-- I always pin certain tabs.
-- Note that because pinning behaves differently in Chrome and Safari, I don't always pin my pin-desired tabs when I move them to Safari.
on shouldPinMe(uri)
	
	tell application "Safari" to set wCount to count of windows
	if wCount > 1 then return false -- pinned tabs are stupid in Safari.  Pinning a tab in one window will add it to ALL safari windows
	
	if (uri as string) = "missing value" then error "no URI in shouldPinMe"
	if uri contains "//pinboard.in" then return true
	if uri contains "//focus.nirvanahq.com" then return true
	if uri contains "//www.nirvanahq.com" then return true
	if uri contains "//workflowy.com" then return true
	if uri contains "//voice.google.com" then return true
	if uri contains "//calendar.google.com" then return true
	if uri contains "//mail.google.com" then return true
	
	return false
end shouldPinMe


-- Pin the tab which is currently front-most in Safari.
on pinActiveTab()
	-- delay 1
	tell application "System Events"
		tell process "Safari"
			set frontmost to true
			click menu item "Pin Tab" of menu "Window" of menu bar 1
		end tell
	end tell
end pinActiveTab


-- Close every Safari tab.
on closeAllSafariTabs()
	tell application "Safari"
		set wCount to count of windows
		repeat with i from 1 to wCount
			set theSafariWindow to window 1
			repeat with theSafariTab in theSafariWindow's tabs
				tell theSafariWindow to close tab 1
			end repeat
		end repeat
	end tell
end closeAllSafariTabs


-- Close every Chrome tab.
on closeAllChromeTabs()
	tell application "Google Chrome"
		set windowList to every tab of every window
		repeat with tabList in windowList
			set tabList to tabList as any
			repeat with tabItr in tabList
				set tabItr to tabItr as any
				delete tabItr
			end repeat
		end repeat
	end tell
end closeAllChromeTabs


-- Return the first n characters of string s.
on leftString(s, n)
	if length of s is less than n then
		return s
	else
		return text 1 thru n of s
	end if
end leftString

Here is Safari-to-Chrome:

-- Move all tabs from Safari to Chrome

-- Optionally close existing Chrome tabs first
tell me to activate
set question to display dialog "Close exisisting Chrome tabs first?" buttons {"Yes", "No"} default button 1
set answer to button returned of question
if answer is equal to "Yes" then
	closeAllChromeTabs()
end if


tell application "Safari"
	repeat with theSafariWindow in windows
		tell application "Google Chrome" to set theChromeWindow to make new window
		repeat with theSafariTab in theSafariWindow's tabs
			set theURL to URL of theSafariTab
			tell application "Google Chrome"
				set theChromeTab to make new tab at end of tabs of theChromeWindow
				set URL of theChromeTab to theURL
				
			end tell
		end repeat
		-- Close empty tab created when making new window.
		tell application "Google Chrome" to close tab 1 of theChromeWindow
		
		
		tell application "Google Chrome" to set tabCount to number of tabs in theChromeWindow
		repeat with i from 1 to tabCount
			tell application "Google Chrome" to set active tab index of first window to i
			tell application "Google Chrome" to set theURL to URL of active tab of front window
			if my ShouldPinMe(theURL) then
				my pinActiveTab()
			end if
		end repeat
		
	end repeat
end tell


tell me to activate
set question to display dialog "Close exisisting Safari tabs?" buttons {"Yes", "No"} default button 2
set answer to button returned of question
if answer is equal to "Yes" then
	closeAllSafariTabs()
end if


-- I always pin certain tabs.
on ShouldPinMe(uri)
	if uri contains "//pinboard.in" then return true
	if uri contains "//focus.nirvanahq.com" then return true
	if uri contains "//workflowy.com" then return true
	if uri contains "//voice.google.com" then return true
	if uri contains "//calendar.google.com" then return true
	if uri contains "//mail.google.com" then return true
	return false
end ShouldPinMe

on pinActiveTab()
	tell application "Google Chrome" to activate
	tell window 1 of application "Google Chrome" to set visible to true
	tell application "System Events"
		tell process "Google Chrome"
			set m1 to (first menu bar item where name is "Window") of menu bar 1
			set mx to menu item "Pin Tab" of menu 1 of m1
			click mx
		end tell
	end tell
end pinActiveTab


on closeAllChromeTabs()
	tell application "Google Chrome"
		set windowList to every tab of every window
		repeat with tabList in windowList
			set tabList to tabList as any
			repeat with tabItr in tabList
				set tabItr to tabItr as any
				delete tabItr
			end repeat
		end repeat
	end tell
end closeAllChromeTabs


on closeAllSafariTabs()
	tell application "Safari"
		set wCount to count of windows
		repeat with i from 1 to wCount
			set theSafariWindow to window 1
			repeat with theSafariTab in theSafariWindow's tabs
				tell theSafariWindow to close tab 1
			end repeat
		end repeat
	end tell
end closeAllSafariTabs

How to Enter Special Characters on a Mac Keyboard

My wife was chatting with someone who asserted that he could not enter a ‘#’ because he’s using a Mac. He later refined his argument to be because he’s using a British Mac and his shifted-3 is a ‘£’. Finally, he refined his argument to be because he couldn’t be bothered to put forth the effort.

Spoiler: On British/American keyboards, use Shift-3 or Alt-3 to get # or £.

This led me to wonder how one enters special characters on a Mac. I’m familiar with the system character chooser on Windows. How does Mac do this?

One-time setup:

  • Go to ‘System Preferences’ and open the Keyboard applet. Select the ‘Input Sources’ tab.
  • Using the ‘+’ button, add any missing items from the following list:
    • British
    • Dvorak (If you use it. I do.)
    • U.S.
    • Unicode Hex Input
  • If you have trouble locating any of the items above, use the Search field.
  • Be sure to put a checkmark in the “Show input menu in menu bar”. This will add an icon to you menu bar.
    • Be sure you know which one it is. You can toggle the checkmark off and on to check which icon.
  • This is optional, but spiffy:
    • Select any one of the keyboards which you’ve added with the ‘+’. Mac will display that layout to the right. Ho hum.
    • Press and hold the Shift key. Mac will show you the shifted form of the layout.
    • Press and hold the Alt key. Mac will show what happens if you enter the Alt form of that keystroke.
    • You can do this for any of the installed keyboard layouts.

Using alternate keyboard layouts

One approach:

  • Click the input menu in the menu bar. (Mine says “DV” because I use Dvorak. Yours is probably the flag for your country.)
  • Choose “Show Keyboard Viewer”. You’ll see a keyboard image.
  • Hold down Alt to see the alt form of each key.
  • If you see the one you want, press it!

Another approach: This one works for esoteric keystrokes.

  • Click the input menu in the menu bar. Choose “Show emoji and symbols”.
  • Browse through the menus to find the symbol you want and double-click it to ‘type’ that symbol. 😀

Setting Up Eclipse

Setting up Eclipse for Java, Python, and Clojure

  • Ensure you have Java 8 JDK (run “javac -version”)
  • Download Eclipse from https://www.eclipse.org/downloads/
    • Select “Download Packages” and install “Eclipse IDE for Java Developers”.
    • I got the “Oxygen 1a” version.
  • Launch Eclipse
    • I set my workspace to ~/Sync/Code and I made it my default location.
    • Help > Eclipse Marketplace > search for pydev. Install pydev (all features). Restart when asked.
    • Help > Eclipse Marketplace > search for StartExplorer. Install. It is unsigned. Restart when asked.
    • Help > Eclipse Marketplace > search for counterclockwise. Install. It is unsigned. Restart when asked.
  • Note: It takes about 8 seconds to launch before installing Counterclockwise. CC adds another 3-4. Slow for an editor, but OK for an IDE.
  • General Eclipse Configuration:
    • In the upper-right corner, to the right of “Quick Access”, you’ll see some icons. Right-click on any of them except for the first one. Choose “Show text”. These icons allow you to quickly switch Eclipse “perspectives.”
    • Install “Eclipse Moonrise UI Theme” via Help > Eclipse Marketplace.
    • Install Eclipse Color Theme plugin.
    • Preferences > General > Appearance. Select “Appearance” and set the theme to “Moonrise (standalone)”. There is a “Color Theme” in the hierarchical menu. Don’t use that one for this step. Choose Appearance and ON THAT TAB select the theme.
    • Preferences > General > Appearance > Color Theme. Select Color Theme from the hierarchical menu; not on the Appearance tab. Select “Vibrant Ink” color theme. You have to do both themes in order to get the ancilliary panes to be dark. Vibrant Ink only colors the editor.
    • I was getting black on black in the Package Explorer.
      • Preferences > General > Appearance > Colors and Fonts. Search for “Uncommitted Change (Foreground)”. I set it to FFADA8. This fixed some nodes in Package Explorer. Editing “Ignored Resources (Foreground)” got the others.
    • Still about 12 seconds to launch Eclipse…
  • Clojure Eclipse Configuration:
    • Preferences > Clojure > Editor >
      • Editor text autoshift
      • Highlight matching brackets
      • Displayed tab width = 2
      • On file launch, switch the Repl…
      • Start editors in strict/paredit mode
      • DO NOT: Escape text when pasting…
      • Tab reindents the current line
      • Auto activate code completion
      • Display namespace in tabs instead of file name
    • Preferences > Clojure > General. Turn OFF “Launch REPLs with cider-nrepl”. Otherwise, your REPL will just die for no reason, after being idle for a few minutes and at random times. It is a known problem.
    • Mac users - REPL command history is via Ctrl-UpArrow and Ctrl-DownArrow. These have Mac meanings, so redefine them to use Cmd instead of Ctrl:
      • Preferences > General > Keys. Search for “command from repl”.
  • Eclipse elements to be familiar with:
    • Java perspective
      • Package Explorer - Left panel. Browses the file system.
        • To launch a REPL without loading a file: Select the project in the Package Explorer; Run > Run Configurations > Clojure > (pick a configuration) > Run.
          • Source code - Main panel in the middle. IF a repl is running, you get fly-over syntax help. (Clojure > Load file in repl. There are other ways to launch a repl, but you don’t seem to get fly-over unless you load the file in the repl.)
          • Outline (a.k.a. namespace browser) - Right panel. Lists the functions in the currently selected source file. Use the sort icon at the top, to choose between show-in-alpha-sequence and show-in-source-file-sequence.
          • Bunch-of-views-at-bottom-of-page: There are several here in a single panel.
            • The ones I find useful:
              • Console - shows output from println.
              • REPL
            • The ones I closed:
              • Problems, Javadoc, Declaration

Things to do or look into:

  • Alt-L brings up a Leiningen menu. You can Launch Headless REPL. You can run ‘lein anything here’ in the current project directory.
  • You can generate a new Clojure project via File > New > Clojure Project, but it uses the ‘default’ project, which is a library. You probably want to drop to the command prompt and use lein new app app-name-here
  • When you generate a new Clojure project from Eclipse, it appears to always put clojure 1.6 in the generated project.clj. Find this line in project.clj and set it to the Clojure version you are using: :dependencies [[org.clojure/clojure "1.6.0"]]
  • Note: It takes almost 20 seconds to launch a Clojure repl in Eclipse. Can I speed that up?
  • Refactor this page, to move getting-started-with [Java/Python/Clojure]-in-eclipse to separate pages.
  • Look into Clojure JUnit integration via https://github.com/mikera/cljunit

Java Hello World With Visual Studio Code and Eclipse

Hello World in Java with Visual Studio Code

  • Download and install a JDK. (Look for “Java SE Development Kit”.) If you don’t get an error when you type “javac -version” at a Command Prompt (Terminal window in Mac), you already have a JDK.
  • Download and install Visual Studio Code (VSCode) from https://code.visualstudio.com/download
  • Launch VSCode
  • Tell VSCode to create a new file via File > New
  • Paste the following text
public class test1 {
    public static void main(String[] args) {
        System.out.println("Hello, World");
        System.out.println("Goodbye, World");
    }
}
  • File > Save As > test1.java
    • VSCode will prompt you to install the Java extension pack. Do so. It will take a few minutes.
      • When it finishes installing, it will show a “Reload” indicator. Click it.
      • It might give you a “Classpath is incomplete” warning.
        • This happens every time you work with a stand-alone Java file (i.e. a Java file that is not part of a project.)
        • For now, just dismiss the warning.
  • View > Integrated Terminal
    • cd to the directory where you saved test1.java
    • javac test1.java
      • ‘javac’ is the compiler. ‘java’ is command which used to run a compiled program.
    • Do a directory listing. Observe that javac compiled test1.java to test1.class.
    • java test1
      • It should display “Hello, World”

Note that you will almost never create a Java program this way. This is a special process for a single-source-file Java app. Typically, your app will be comprised of many files, and you’ll have to create a ‘project’ to tie them all together. Most Java projects are godawful complex things which require a ‘build tool’ to compile and assemble them into something you can run. There are multiple build tools which Java developers use because developers often say, “This is awful. I could build something better.”

We’re going to use Maven. Maven is good for our purposes because VSCode works with it and so does Eclipse, in case you later decide you have to suffer Eclipse. Maven revolves around a ‘POM file’ (Project Object Model), which is written in XML.

We’re going to abandon this stand-alone source file and create a hello-world Maven project; we’ll also set you up to use the Java debugger.

  • Close VSCode.
  • For Mac
    • Run “brew install maven”. It will install to /usr/local/bin/mvn.
    • Run mvn --version
    • Add this to .bash_profile, substituting the value for Maven home as reported by mvn (above).
      • export M2_HOME=/usr/local/Cellar/maven/3.5.2/libexec
      • export M2=$M2_HOME/bin
      • export PATH=$PATH:$M2_HOME/bin
  • For Windows, download and unzip Maven from http://maven.apache.org/download.cgi.
    • You want a “Binary zip archive”. Choose the link from the “Link” column to start the download.
    • Store it in a directory all by itself. Maybe name that directory “MAVEN”.
    • Set a M2_HOME environment variable to point to the MAVEN directory.
    • Set a M2 (not M2_HOME - just ‘M2’) environment variable to point to the bin subdirectory of the MAVEN directory.
    • Add the M2 (bin) directory to your PATH.
  • In your Terminal window (Command Prompt), navigate to an empty directory, where you wish to create your Java project.
    • mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
      • This may take a long time.
    • cd to my-app/src/main/java/com/mycompany/app. (If you’re on Windows, your slashes lean the other way.)
    • Run a directory listing and you’ll see App.java. Examine the contents. You’ll see that Maven created a hello-world app for you. This godawful directory structure – that’s how Java apps are built.
  • Launch VSCode and open the my-app directory from the File menu.
    • In the left panel of VSCode, navigate to my-app/src/main/java/com/mycompany/app, and open App.java
    • Find the line with System.out.println. Left-click with your mouse, just to the left of the line number. You should see a red circle. You’ve just set a breakpoint on this line.
    • Choose Debug > Start Debugging from the menu. If it asks what kind, select Java.
    • It will highlight the line with your breakpoint. It executed all the code before that point, and stopped.
    • Choose Debug > Step Over. It will execute your println, and you’ll see the output displayed in the bottom panel.
    • Choose Debug > Continue. It will ‘execute’ all those closing braces and terminate your program normally.

Hello World in Java with Eclipse

Install and Launch LiClipse

We’re going to use LiClipse, which is Eclipse bundled with some other tools.

  • Download and install a JDK. (Look for “Java SE Development Kit”.) If you don’t get an error when you type “javac -version” at a Command Prompt (Terminal window in Mac), you already have a JDK.
  • Download and install LiClipse from http://www.liclipse.com/download.html
  • Launch LiClipse (henceforth to be referred to simply as Eclipse).
  • Eclipse wants to use a “workspace”. That’s the root folder for all projects you develop using Eclipse. Pick one that suits you. On my Mac, I use /Users/kevin/Sync/code. Unless you want it to nag you each time you launch it, select “Use this as the default and do not ask again”.
  • Help > Install New Software > select “Eclipse x.x Release…”
    • In the “Type Filter Text” field, type “java” and press Enter.
    • “Select Eclipse Java Development Tools” and press Next; then work you way through the Wizard until it is installed.
    • Watch the progress dialog in the lower-right corner of Eclipse. Don’t proceed until it hits 100%. It may be sloooow.
    • When it wants to restart Eclipse, let it.

Create and Run a Single-file Java App

You really, really have to put your single-file Java app in a project. If you don’t, you’ll find yourself unable to save your file. Eclipse understands projects. It doesn’t really deal with stand-alone files.

  • File > New > Project > Java > Java Project. Press Next.
    • Enter a project name of test1.
    • Choose “Use Project folder as root for sources and class files”
    • Press Finish.
    • It will natter about opening a Java Perspective. Let it, and tell it to always do so. It is just going to open the panels which are relevant to Java.
  • File > New > Class
    • Source folder = test1
    • Package = (empty)
    • Name = test1
    • Superclass = (empty)
    • Clear ALL checkboxes
    • Press Finish
  • Make the test1.java file contain:
public class test1 {
    public static void main(String[] args) {
        System.out.println("Hello, World");
        System.out.println("Goodbye, World");
    }
}
  • File > Save
  • Locate the toolbar icon for “Run”. (One of the icons with a green circle with a triangle.) Press it.
    • It will natter at you about Run Configurations.
      • Double-click “Java Application” and choose test1 (under Java Application).
      • Press the Run button.
    • Notice your output at the bottom of the page.

Note that you will almost never create a Java program this way. This is a special process for a single-source-file Java app. Typically, your app will be comprised of many files, and you’ll have to create a ‘project’ to tie them all together. Most Java projects are godawful complex things which require a ‘build tool’ to compile and assemble them into something you can run. There are multiple build tools which Java developers use because developers often say, “This is awful. I could build something better.”

We’re going to use the built-in Eclipse build too. If you ever need to do so later, Eclipse can also work with Maven projects and it can export Eclipse projects into Maven ‘POM files’. Maven revolves around a POM file (Project Object Model), which is written in XML.

We’re going to abandon this stand-alone source file and create a new hellow-world project; we’ll also use the Java debugger.

  • File > New > Java Project. Note that because you are already in the Java Perspective, Eclipse has hoisted ‘Java Project’ into the top-level menu. You can get the same effect via File > New > Project > Java > Java Project
    • Project Name = my-app2
    • Use default location
    • JRE: use whatever it defaults to
    • Project Layout: Create separate folders…
    • Press Finish
  • Stop and look.
    • You should now see TWO Java projects in the Package Explorer panel at the left side of Eclipse.
    • Sometimes, if one Java project is not causing you enough pain, Eclipse figures you might want to work on multiple projects at the same time. Getting started, one at a time is enough.:
      • Select my-app2. Then right-click it and choose “Close Unrelated Projects”. Tell Eclipse that you really meant to, when it asks.
      • You will still see a single folder for test1, even though it is closed. That’s how Eclipse works.
  • Select the src folder under my-app2.
  • Right-click src and create a new package:
    • Name it com.mycompany.app and press Finish.
  • Right-click com.mycompany.app and create a new class:
    • Source folder: my-app2/src
    • Package: com.mycompany.app
    • Name: App
    • Modifiers: Public
    • Superclass: java.lang.Object
    • Select “public static void main…” but leave the other checkboxes empty.
    • Press Finish
  • Take a look at the generated App.java file. It is almost a complete hello-world. Replace the TODO comment with:
        System.out.println("Hello, World");
        System.out.println("Goodbye, World");
  • File > Save
  • Locate the toolbar icon for “Run”. (One of the icons with a green circle with a triangle.) Press it.
    • It will natter at you about Run Configurations.
      • Double-click “Java Application” and choose test1 (under Java Application).
      • Press the Run button.
    • Notice your output at the bottom of the page.
  • Double-click the gutter, to the left of the line number by the first line with System.out.println. It will add a small dot to indicate that you have set a breakpoint on this line.
  • Locate the toolbar icon of an insect and the flyover help “Debug App”. Press it.
    • Eclipse will ask permission to switch to the Debug Persipective. Approve it.
    • It will highlight the line with your breakpoint. It executed all the code before that point, and stopped.
    • Choose Run > Step Over. It will execute your println, and you’ll see the output displayed in the bottom panel.
    • Choose Run > Resume. It will ‘execute’ all those closing braces and terminate your program normally.
  • Note that if you ever find Eclipse in the ‘wrong’ Perspective, you can change Perspective via Window > Perspective > Open Perspective.