Challenge #55 is for an xxd clone.
Complete the challenge using Swift as a command line app for macOS.
-
use XCode tooling and standard Swift based libraries as much as possible, e.g. logging, testing frameworks etc.
-
I've used Swift on projects before for building Apps but it's been a while. Hopefully this will refresh the Swift basics for me.
-
Things that I see as challenges in this effort:
- Stucts vs Classes
- Swift I/O
- Command Line argument processsing
- Getting lost in how much Swift has evolved since I last used it
-
Although we're using Swift this should be runnable under Linux with the proper compilier/libraries
- XCode 15.3
-
Set up development enviornment
- Create XCode command line progject
- init git repo locally and on Github
-
Run "hello world"
-
run command line tool from terminal
Product
->Show Build Folder in Finder
-
figure out command line processing in Swift
- The Swift
ArgumentParser
package looks like the way to go - Add by
File
->Add Package Dependencies...
- Generates a
Package.swift
file with the approperiate entries somewhere in the project presumabally
- Generates a
- Turns out that using
ArgumentParser
on a struct with other non-argument properties causes all kinds of hell withDecoder
. Not something I enjoyed tracking down right out of the gate
- The Swift
-
figure out testing framework in Swift
- XCTest is the built in one for XCode but there are other frameworks out there
- This was WAY HARDER than I was expecting. I could not figure out how to get it to include the
struct
I wanted to test. Turns out I had to add the file with thestruct
under test to the test "target".
-
figure out logging in Swift
- Logger struct is evidenlty the way to go
- This was where I ran into problems with the commnad line processing
-
Rewrite "hello world" to use commandline, logging and testing
@main
explained- Can not use
@main
in a file calledmain
.. stupid
- Can not use
- as I mentioned earlier, getting through this took way longer. Really fighting documentation issues. Phind helped but still lots of struggles. At least I'm ready to move on to step 1...
Basically dump some input file out as "hex" like xxd
does
- need a command line argument to read the file
- format the output in 3 sections:
- offset from beginning of the file, in hex - 8 bytes long
- the hex output - 16 bytes long in a default of 4 "octets"
- the ascii output - also 16 bytes but in char form. anything outside of printable chars is a
.
-
Opted to read the file in "chunks" of 16 x UInt8 using an extension to FileManager
-
For each chunk, generate a String representation from the printable characters - as an extention to the String type
-
Also for each chunk - divide into an array of arrays represeting the desired octet output "grouping". This is what we'll need in Step 2, but got that out of the way in this step. Again, used an extension this time with the Array type
-
For those grouped items, generate a hex representation for outut. Leveraged an extension in Array type
-
Keep track of the file offset and output that as hex
-
learned a lot about using map & reduce functions in iterating over the array of bytes and array of arrays!
-
Feels like there is too much struct creation going on for moving bytes around.
- need some way to observe memory pressure/use
-
also got to be a better way to conver a byte into a char for concat into a string. I do need to do that one byte at a time for printable character reasons, but that's a lot of iterating
-
Basically feeling like it works but is sub optimal - although the examples I've looked at basically do what the codes doing. Who knows
This step is about supporting the -e (little-endian) flag and -g (group) option
ArgumentParser
library should come in handy here. Probably use some of the more specific syntax- Grouping should already be done through the "chunking" call
- I'll need to come up with a strategy for converting to the
[UInt8]
chunks by swapping around bytes based on the chunk size- will also need to validate that the "group" is divisible by 2 if the endian option is used
- added flags and options to the command line to handle the input
- the grouping was pretty easy since I already had the chunking figured out
- ended up writing a
toLittleEndian()
extension for[UInt8]
and added a if statement to handle the flag
support the command line options to set the number of octets to be written out and the number of columns to print per line. These are the -l and -c flags [if] you want to explore the valid settings in the man entry.
-
the "len" option is just how many total octets to read from the file
- Added
OctetCounter
to help with octet bookkeeping - Adding padding to the hex output so incomplete lines will line up
- Added
-
the "col" option is how many octets to read at once.
- up to this point, 16's been the default column size so that needs to become flexible
OctetCounter
now controls the size of the read, which is the number of "cols"
- again, given the existing structure, this wasn't all that much of a change. Mainly rerouting some constants already in use.
- consolidating the logic in the
OctetCounter
make it pretty easy - not sure I have the logic right between
-g
,-e
and-c
but the standardxxd
seems to ignore-g
when-c
is in use
support the -s flag which allows us to seek to a specific byte offset in the file
- This was easy as well. Just added the
@Option
and hooked it into the logic I already anticipated for seeking to a specific offest in the file to begin reading - It also appears that the
swift-argument-parser
got wonky in the build process. Had to readd it.
support the ability to revert a hex dump back to a binary file. This is the -r option.
So:
- Read in the file line at a time
- for earch line:
- parse out what's between the line count and the ASCII field
- remove/trim any space - should be a hex string at this point
- convert the hex string to a series of bytes in an array
- this as part of the testing infrastructure!!
- write that out to standard out as binary(??)
Might be easier than I though.. so regex (ya I know I have 2 problems now) and moving a testing extension into the main code.
I may have to break off the main code into 2 function.. one for regular processing and one for reverse processsing
- Mainly correct except I stumbled a bit figuring out how to write out a sequence of bytes.
print
doesn't work well w/binary data so I had to usewrite
tostdout
- Not impressed with the RegEx "builder" stuff. Way too wordy for my taste. I guess if you don't know regex it might be more helpful but w/30 years of regex behind me I wasn't going to do the builder pattern for what I can put in a string. This converter was kind of interesting.
- Swift has a way to go with handling file I/O with simple things like reading a line of data. Should not be that hard.
- Also not impressed with all the byte manipulation to/from UInt8s to various things. Swift might allow access to low level Bytes, but it doesn't make it easy.
- Interesting project. I didn't know a lot about
xxd
but it's an interting utility. - Enjoyed writing a lot of what I needed as Swift
extension
. - Testing framework could be better
- Command line processing could be better
- File I/O could be better
- The amount of abstraction/structure is staggering in Swift. Python was a lot easier as was Objective-C.
- Getting compitant at Swift will take some work.
- I'm sure a lot of this could be optimized but for now I'm calling #55 done!