Skip to content

Instantly share code, notes, and snippets.

@kambala-decapitator
Created April 16, 2018 09:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kambala-decapitator/695f663c3e508ef70da29c923c0b7261 to your computer and use it in GitHub Desktop.
Save kambala-decapitator/695f663c3e508ef70da29c923c0b7261 to your computer and use it in GitHub Desktop.
Small macOS CLI program to list filesystem items
//
// main.m
// FSTraversal
//
// Created by Andrey Filipenkov on 03/03/17.
// Copyright © 2017 Andrey Filipenkov. All rights reserved.
//
@import Foundation;
#include <libgen.h>
#define MEASURE_TIME 0
static NSString *const DepthArgument = @"depth";
static NSString *const HeightArgument = @"height";
static NSString *const DirSeparator = @"/";
static NSString *const RootRelativePath = @"/";
@interface FSItem : NSObject
@property (nonatomic, copy) NSString *relativePath;
@property (nonatomic, assign) NSUInteger depth;
@property (nonatomic, assign) NSUInteger height;
@property (nonatomic, weak) FSItem *parent;
+ (instancetype)itemWithRelativePath:(NSString *)relativePath depth:(NSUInteger)depth parent:(FSItem *)parent;
@end
@implementation FSItem
+ (instancetype)itemWithRelativePath:(NSString *)relativePath depth:(NSUInteger)depth parent:(FSItem *)parent
{
FSItem *item = [self new];
item.relativePath = relativePath;
item.depth = depth;
item.parent = parent;
return item;
}
@end
int main(int argc, const char *argv[])
{
@autoreleasepool
{
if (argc < 2)
{
printf("usage: %s <path> [-%s <depth> -%s <height>]\n", basename((char *)argv[0]), DepthArgument.UTF8String, HeightArgument.UTF8String);
return 0;
}
NSString *rootPath = @(argv[1]).stringByStandardizingPath;
if (![[NSFileManager defaultManager] fileExistsAtPath:rootPath])
{
fprintf(stderr, "filesystem item doesn't exist: %s\n", rootPath.UTF8String);
return 1;
}
#if MEASURE_TIME
NSDate *startDate = [NSDate new];
#endif
NSUInteger relativePathStartIndex = rootPath.length;
if (relativePathStartIndex == 1) // we're searching in system volume root
relativePathStartIndex = 0;
NSString *(^relativePath)(NSString *path) = ^NSString *(NSString *path) {
return [path substringFromIndex:relativePathStartIndex];
};
// TODO: parse with NSScanner to know if an arg can actually be converted into a number
// TODO: don't use NSUserDefaults to read args as it doesn't work with negative numbers
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL hasFilterArgs = [defaults objectForKey:DepthArgument] != nil && [defaults objectForKey:HeightArgument] != nil;
NSInteger depthArg = [defaults integerForKey:DepthArgument], heightArg = [defaults integerForKey:HeightArgument];
BOOL computingForRootPath = depthArg == 0;
NSMutableArray<FSItem *> *__block fsItems = [NSMutableArray new];
NSMutableDictionary<NSString *, FSItem *> *parentFSItemsDic;
if (!hasFilterArgs || computingForRootPath)
{
FSItem *rootItem = [FSItem itemWithRelativePath:RootRelativePath depth:0 parent:nil];
[fsItems addObject:rootItem];
if (!hasFilterArgs)
parentFSItemsDic = [NSMutableDictionary dictionaryWithObject:rootItem forKey:RootRelativePath];
}
FSItem *__block currentFilteredItem = fsItems.lastObject;
void(^maybeRemoveLastItem)() = !hasFilterArgs ? nil : ^{
if (currentFilteredItem && currentFilteredItem.height < heightArg)
[fsItems removeLastObject];
};
NSUInteger lastDepth = 0;
NSDirectoryEnumerator<NSURL *> *dirEnum = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:rootPath]
includingPropertiesForKeys:hasFilterArgs ? nil : @[NSURLParentDirectoryURLKey]
options:kNilOptions errorHandler:nil];
for (NSURL *url in dirEnum)
{
NSUInteger currentDepth = dirEnum.level;
if (hasFilterArgs && currentDepth < depthArg)
continue;
@autoreleasepool
{
if (hasFilterArgs)
{
if (currentDepth == depthArg)
{
maybeRemoveLastItem();
[fsItems addObject:[FSItem itemWithRelativePath:relativePath(url.path) depth:currentDepth parent:nil]];
currentFilteredItem = fsItems.lastObject;
}
else
{
if (currentFilteredItem)
{
NSUInteger height = currentDepth - depthArg;
if (height > heightArg)
{
currentFilteredItem = nil;
[fsItems removeLastObject];
if (computingForRootPath)
break;
else
[dirEnum skipDescendants];
}
else
{
if (currentFilteredItem.height < height)
currentFilteredItem.height = height;
}
}
else
[dirEnum skipDescendants];
}
continue;
}
NSURL *parentUrl;
[url getResourceValue:&parentUrl forKey:NSURLParentDirectoryURLKey error:NULL];
NSString *relativeParentPath = relativePath(parentUrl.path);
if (!relativeParentPath.length)
relativeParentPath = RootRelativePath;
FSItem *parentItem = parentFSItemsDic[relativeParentPath];
if (!parentItem)
parentItem = parentFSItemsDic[relativeParentPath] = fsItems.lastObject;
[fsItems addObject:[FSItem itemWithRelativePath:relativePath(url.path) depth:currentDepth parent:parentItem]];
if (lastDepth == currentDepth)
continue;
lastDepth = currentDepth;
for (NSUInteger height = 1; parentItem != nil; ++height, parentItem = parentItem.parent)
{
if (parentItem.height < height)
parentItem.height = height;
else
break;
}
}
}
if (maybeRemoveLastItem)
maybeRemoveLastItem();
#if MEASURE_TIME
NSTimeInterval searchTime = -startDate.timeIntervalSinceNow;
#endif
if (fsItems.count)
{
for (FSItem *item in fsItems)
{
printf("%s", item.relativePath.UTF8String);
if (!hasFilterArgs)
printf(" %lu %lu", item.depth, item.height);
printf("\n");
}
}
else
printf("no results matching given criteria have been found\n");
#if MEASURE_TIME
printf("%lu filesystem items, traversal time %.2lfs, printing time %.2lfs\n", fsItems.count, searchTime, -startDate.timeIntervalSinceNow - searchTime);
#endif
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment