Adding a dropdown calendar to the macOS desktop with Itsycal
26th January 2026In Linux Mint, there is a dropdown calendar that can be used for some advance planning. On Windows, there is a pop-up one on the taskbar that is as useful. Neither of these possibilities is there on a default macOS desktop, and I missed the functionality. Thus, a search began.
That ended with my finding Itsycal, which does exactly what I need. Handily, it also integrates with the macOS Calendar app, though I use other places for my appointments. In some ways, that is more than I need. The dropdown pane with the ability to go back and forth through time suffices for me.
While it would be ideal if I could go year by year as well as month by month, which is the case on Linux Mint, I can manage with just the latter. Anything is better than having nothing at all. Sometimes, using more than one operating system broadens a mind.
Switching from uBlock Origin to AdGuard and Stylus
15th January 2026A while back, uBlock Origin broke this website when I visited it. There was a long AI conversation that left me with the impression that the mix of macOS, Firefox and WordPress presented an edge case that could not be resolved. Thus, I went looking for alternatives because I may not be able to convince else to look into it, especially when the issue could be so niche.
One thing the uBlock Origin makes very easy is the custom blocking of web page elements, so that was one thing that I needed to replace. A partial solution comes in the form of the Stylus extension. Though the CSS rules may need to be defined manually after interrogating a web page structure, the same effects came be achieved. In truth, it is not as slick as using a GUI element selector, but I have learned to get past that.
For automatic ad blocking, I have turned to AdGuard AdBlocker. Thus far, it is doing what I need it to do. One thing to note is that does nothing to stop your registering in website visitor analytics, not that it bothers me at all. That was something that uBlock Origin does out of the box, while my new ad blocker sticks more narrowly to its chosen task, and that suffices for now.
In summary, I have altered my tooling for controlling what websites show me. It is all too easy for otherwise solid tools to register false positives and cause other obstructions. That is why I find myself swapping between them every so often; after all, website code can be far too variable.
Maybe it highlights how challenging it is to make ad blocking and other similar software when your test cases cannot be as extensive as they need to be. Add in something of an arms race between advertisers and ad blockers for the ante to be upped even more. It does not help when we want the things free of charge too.
Finding a better way to uninstall Mac applications
14th January 2026If you were to consult an AI about uninstalling software under macOS, you would be given a list of commands to run in the Terminal. That feels far less slick than either Linux or Windows. Thus, I set to looking for a cleaner solution. It came in the form of AppCleaner from FreeMacSoft.
This finds the files to remove once you have supplied the name of the app that you wish to uninstall. Once you have reviewed those, you can set it to remove them to the recycling bin, after which they can be expunged from there. Handily, this automates the manual graft that otherwise would be needed.
It amazes me that such an operation is not handled within macOS itself, instead of leaving it to the software providers themselves, or third-party tools like this one. Otherwise, a Mac could get very messy, though Homebrew offers ways of managing software installations for certain cases. Surprisingly, the situation is more free-form than on iOS, too.
Locking your computer screen faster using keyboard shortcuts
11th January 2026When you are doing paid work on a computer, locking one's screen is a healthy practice for ensuring privacy and confidentiality while you are away from your desk for a short while. For years, I have been doing this on Windows using the WIN (Windows key) + L keyboard combination. It is possible on a Mac too, albeit using a different set of keys: CTRL (Control) + CMD (Command) + Q. While the Lock Screen item on the Apple menu will accomplish the same result, a simple keyboard shortcut works much, much faster. On Linux, things are a lot more varied with different desktop environments working in their own way, even making terminal commands a way to go if you can use a heavily abbreviated alias.
Streamlining text case conversion across multiple apps with a reusable macOS terminal command
10th January 2026Changing text from mixed case to lower case is something that I often do. Much of the time until recently, this has been accomplished manually, but I started to wonder if a quicker way could be found. Thus, here is one that I use when working in macOS. It involves using a command that works in the terminal, and I have added a short alias for it to my .zshrc file. Here is the full pipeline:
pbpaste | tr '[:upper:]' '[:lower:]' | pbcopy
In the above, pbpaste reads from the paste buffer (or clipboard) while pbcopy writes the final output to the clipboard, replacing what was there before. In between those, tr '[:upper:]' '[:lower:]' changes any lower case letters to lower case ones.
With that, the process becomes this: copy the text into the paste buffer, run the command, paste output where it is wanted. While there may be a few steps, it is quicker than doing everything manually or opening another app to do the job. This suffices for now, and I. may get to look at something similar for Linux in time.
How to persist R packages across remote Windows server sessions
9th January 2026Recently, I was using R to automate some code changes that needed implementation when porting code from a vendor to client systems. While I was doing so, I noticed that packages needed to be reinstalled every time that I logged into their system. This was because they were going into a temporary area by default. The solution was to define another location where the packages could be persisted.
That meant creating a .Renviron file, with Windows Explorer making that manoeuvre an awkward one that could not be completed. Using PowerShell was the solution for this. There, I could use the following command to do what I needed:
New-Item -ItemType File "$env:USERPROFILE\Documents\.Renviron" -Force
That gave me an empty .Renviron file, to which I could add the following text for where the packages should be kept (the path may differ on your system):
R_LIBS_USER=C:/R/packages
Here, the paths are only examples and do not always represent what the real ones were, and that is by design for reasons of client confidentiality. Restarting RStudio to give me a fresh R session meant that I now could install packages using commands like this one:
install.packages("tidyverse")
Version constraints meant for compilation from source in my case, making for a long wait time for completion. Once that was done, though, there was no need for a repeat operation.
One final remark is that file creation and population could be done in the same command in PowerShell:
'R_LIBS_USER=C:/R/packages' | Out-File -Encoding ascii "$env:USERPROFILE\Documents\.Renviron"
It places the text into a new file or completely overwrites an existing, meaning that you really want to do this once should you decide to add any more setting details to .Renviron later on.
Not so fast: When tasks using AI may take more time and attention than you expect
29th November 2025If you believed all the hype that surrounds AI, you might believe that all of us would out of work before we knew it. The truth is that the new technology is not that miraculous, especially when based on some experiences that I have been having. Firstly, there are deficiencies and then there will be new things that need doing as well as becoming possible for the first time.
PowerShell Scripting
One pertained to spinning up PowerShell scripts for doing code reviews of SAS programs submitted by a vendor to a client of mine. While all worked well for simple cases, I found that more complex tasks like finding the datasets using in code and comparing them against what is listed in the program headers became too complicated and probably needed a week of my time to get things in order, which was the amount of time that I did not have.
Picking out macro calls from code and comparing them against lists in the headers was more successful because the code situations were less variable. Other tasks were really handy, though, even if I would benefit from AI teaching me how to write PowerShell scripts by myself. That would give me more scope to critique the code that was being produced. Starting simple and progressing one step at a time would ensure sounder embedding of PowerShell commands in my memory.
Article Writing
It is all too tempting to get AI to write articles on subjects of your choosing for website content production. That which sounds like a labour-saving way to go can command a higher amount of attention than some realise. Sometimes, writing it all by yourself might be a better approach, one that I am using for this piece.
My workflow often involves these steps when AI is involved: assembly of the source material, conversion of source material into an article by one AI, fact checking of the same text by another AI and restructuring by that second AI with added links for those wanting to find out more. While human content production is reduced, the need for human oversight, along with fact and link checking, means that time is used in other ways.
In short, it is best not to rush this, as I found when assembling two articles on Canadian rail travel. You also need to watch how much content is being processed because that can both overwhelm human bandwidth and undermine human engagement. This is more than proofreading of what is produced; you need to know something about a given subject yourself too.
Image Production
While AI can do well with producing some images, there are ones where it will struggle because of lack of training. An example is when I asked for an image with cyclists placing bicycles on a bus before boarding it. None of the generated images worked, meaning that a trip to a stock library was in order.
While some can specify everything in a prompt at one sitting, I work more iteratively, which probably adds to any task, especially with image generation. It proves that still is a place for stock libraries and having your own personal library as well. We need to remain as orchestrators in all of this, and lack of personal talent can remain a limitation.
System Administration
While this may not be something that I do professionally, my keeping an eye on the worlds of DevOps and DevSecOps means that I am seeing that the presence of AI is adding work of its own. This has no sign of lessening, proving that work is changing dramatically instead of reducing, especially you bring Agentic AI into the equation.
It feels much like the advent of personal computing and that produced a similar seismic shift in the workplace in more innocent times. This time around, nefarious actors are misusing AI, a not unexpected if ominous trend, adding to the security woes that have beset computing for a few decades now.
A Human in the Loop?
At a recent conference, much was being made of keeping humanity in the loop when it came to using AI. There is a catch, though: how do we have engaged humans in the loop? After all, creating computer code allows one to get into flow and remain engaged, possibly overriding any feelings of fatigue. This is what needs replicating, hardly an experience reported with automation in other professions.
The use of AI is a developing field, bringing new challenges as well as solving old problems. That also means upskilling on a grand scale, something happened over time with personal and business computing. While it looks as if the process could be faster this time around, it is too early to know enough about where this revolution is going to take us. That may be enough to keep us engaged.
Launching SAS Analytics Pro on Viya with automated Docker image clean-up
28th November 2025For my freelancing, I have a licensed version of SAS Analytics Pro running in a Docker container on my main Linux workstation. Every time there is a new release, a new Docker is made available, which means that a few of them could accumulate on your system. Aside from taking up disk space that could have other uses, it also makes it tricky to automate the startup of the associate Docker container. Avoiding this means pruning the Docker images available on the system, something that also needs automation.
To make things clearer, let me work through the launch script that I use; this is called by the script that checks for and then downloads any new image that is available, should that be needed. First up is the shebang, and this uses the -e switch to exit the script in the event of there being an error. That puts a stop to any potentially destructive outcomes from later commands being executed afterwards and without having the input that they need.
#!/bin/bash -e
Next comes the command to shut down the existing container. Should a new image get instated, this would lock up the old one, preventing its removal. Also, doing the rest of the steps with an already running container will result in errors anyway.
if docker container ls -a --format '{{.Names}}' | grep -q '^sas-analytics-pro$'; then
docker container stop sas-analytics-pro
fi
After that, the step to find the latest image is performed. Once, I did this by looping through the ages by days, weeks and months, hardly an elegant or robust approach. What follows is something all the more effective.
# Find latest SAS Analytics Pro image
IMAGE=$(docker image ls --format '{{.Repository}}:{{.Tag}} {{.CreatedAt}}' \
| grep 'sas-analytics-pro' \
| sort -k2,3r \
| head -n 1 \
| awk '{print $1}')
echo "Chosen image: $IMAGE"
Since there is quite a lot happening above, let us unpack the actions. The first part lists all Docker images, formatting each line to show the image name (repository:tag) followed by its creation timestamp: docker image ls --format '{{.Repository}}:{{.Tag}} {{.CreatedAt}}'. The next piece picks out all the images that are for SAS Analytics Pro: grep 'sas-analytics-pro'. The crucial step, sort -k2,3r, comes next and this sorts the results by the second and third fields (the creation date and time) in reverse order, so the newest images appear first. With that done, it is time to pick out the most recent image using head -n 1. To pick out the image name, you need awk '{print $1}. This wrapped within IMAGE=$(...) to assign the result to a variable that is printed to the console using an echo statement.
With the image selected, you can then spin up the container once you specify the other parameters to use and allow some sleep time afterwards before proceeding to the clean-up steps:
run_args="
-e SASLOCKDOWN=0
--name=sas-analytics-pro
--rm
--detach
--hostname sas-analytics-pro
--env RUN_MODE=developer
--env SASLICENSEFILE=[Path to SAS licence file]
--publish 8080:80
--volume ${PWD}/sasinside:/sasinside
--volume ${PWD}/sasdemo:/data2
--volume [location of SAS files on the system]:/data
--cap-add AUDIT_WRITE
--cap-add SYS_ADMIN
--publish 8222:22
"
if ! docker run -u root ${run_args} "$IMAGE" "$@" > /dev/null 2>&1; then
echo "Failed to run the image."
exit 1
fi
sleep 5
With the new container in action, the subsequent step is to find the older images and delete those. Again, the docker image command is invoked, with its output fed to a selection command for SAS Analytics Pro images. Once the current image has been removed from the listing by the grep -v command, the list of images to be deleted is assigned to the IMAGES_TO_REMOVE variable.
IMAGES_TO_REMOVE=$(docker image ls --format '{{.Repository}}:{{.Tag}}' \
| grep 'sas-analytics-pro' \
| grep -v "^$IMAGE$")
echo "Will remove older images:"
echo "$IMAGES_TO_REMOVE"
After that has happened, iterating through the list of images using a for loop will remove them one at a time using the docker image rm command:
for OLD in $IMAGES_TO_REMOVE; do
echo "Removing $OLD"
docker image rm "$OLD" || echo "Could not remove $OLD"
done
All this concludes the operation of spinning up a new SAS Analytics Pro Docker container while also removing any superseded Docker images. One last step is to capture the password to use for logging into the SAS Studio interface that is available at localhost:8080 or whatever address and port is being used to serve the application:
docker logs sas-analytics-pro 2>&1 | grep "Password=" > pw.txt
Folding updating and housekeeping into the same activity as spinning up the Docker container means that I need not think of doing anything else. The time taken by the other activities repay the effort by always having the latest version running in a tidy environment. That just saves having to remember to do all of this, which is what is needed without automation.
Blocking unwanted interface elements in ChatGPT with uBlock Origin
27th November 2025This time last year, I was a regular user of Perplexity. Unfortunately, it began to live to its name when news items began to appear on its previously clean home page. When ChatGPT and Anthropic Claude gained the ability to search the web one after another, there was little need to use Perplexity any longer. Before that happened, I began to use uBlock Origin to block the offending panels that I found so intrusive.
However, I still retain an enduring intolerance of intrusions into clean interfaces on public GenAI tools. Thus, when ChatGPT started to offer inspiration for using it in a dropdown panel below the text box, I began to look for ways to block it. It is not as if I need ideas from others anyway; quite enough come up for me from my daily computing.
While disabling memory may help, I sought another way to turn the dropdown panel, only to find that there was none. That left uBlock Origin as my means of control. Unfortunately, OpenAI do not make it easy to block the offending insertion; Perplexity was very simple: right-click on the item and navigate to uBlock Origin > Block element... on the context menu that appears. Making the selection on the ChatGPT interface was unavailable because of how they structure things.
Ironically, I started to pursue the matter using the ChatGPT tool itself. All of this was on Firefox, so I could explore the code by right-clicking on the page and selecting Inspect from the context menu that appeared. Just viewing the source code was not an option either; obfuscation on the OpenAI end saw to that: they appear to use JavaScript to convert indecipherable symbols into code that a browser can render. There was some toing and froing before I got as far as a workable solution.
This needed me to get into the uBlock Origin Dashboard through selecting its icon on the toolbar (while I have it pinned there, you may need to click on the Extensions button in the same place as an additional step before all the steps that I describe here) and then clicking on the gears icon in the bottom right of the panel that appears. Once into the uBlock Origin interface, go to the My Filters tab and add the following code in there:
chatgpt.com##ul.divide-token-border-light.flex-col.divide-y > li.w-full
The first part (before the ## separator) is the URL, which may be chatgpt.openai.com for you. The rest selects the ideas panel while leaving the prompt text and hyperlink in place. That sufficed for me; a generic item is not as intrusive as anything built from your history or any other source of information. Naturally, the interface may change again, which might mean that I need to revisit the filter, but this works for now. We all learn as we go.
Using the LIKE operator in PROC SQL WHERE clauses in SAS
26th November 2025Recently, I was working in SAS and decided to trying picking out datasets and variables from its dictionary tables, eventually picking out the maximum length of a variable type for assigning the length of a new variable. This could have been done using a long-established technique:
proc sql;
select distinct memname into :dsns separated by '#'
from dictionary.tables
where lowcase(libname) = 'work'
and index(lowcase(memname), "r_") = 1
and index(lowcase(memname), "visit") = 0;
quit;
The result is that it creates a macro variable containing a delimited list of work datasets with names beginning with r_ and not containing the string visit. As well as using the index function to find the placing of one string within another, I have seen the count function used for similar purposes, albeit without the placement specificity. Since the =: operator which looks for a search string at the start of a larger is not something that works in SQL (data step is more than fine), you cannot do something like this:
proc print data = sashelp.vmember noobs;
where lowcase(libname) = 'work'
and lowcase(memname) =: "r_";
run;
While the contains operator works similarly to the count function when it comes to search text positioning, yet another option is the like operator, and that is shown in the example below:
proc sql;
select distinct memname into :dsns separated by '#'
from dictionary.tables
where lowcase(libname) = 'work'
and lowcase(memname) like 'r\_%' escape '\'
and lowcase(memname) not like '%visit%';
quit;
Here, % and _ are placeholder characters, with the first matching zero or more characters and the second matching one character. Thus, the underscore in r_ needs escaping to look for that pattern (otherwise, it will look for the letter r at the start of a string and a single character after it) and a backslash character (\) will cover that duty. To ensure that it does what you want, adding escape '\' after the expression tells SAS what is happening.
Another thing to watch is that the percent (%) character needs a form escaping from the SAS Macro language processor, and placing the search term in single quotes attends to that. That means that %visit% does not cause any errors when you are looking for visit within a dataset name and using negation (the not operator) to exclude that possibility from the search results. However, using _%visit% might be a better pattern for finding visit at the end of a name, though.
Should you wish to play around with the above to see what happens for your own learning, try using something like this to give you a few test datasets:
data r_test r_visit visit;
set sashelp.class;
run;
Otherwise, feel free to add your own test cases to cement the ideas even further. All too often, we look up something, deploy it and then forget about, especially when AI is involved. Nevertheless, the fastest way to write code can be to use what is embedded in your memory.