Created
April 16, 2018 09:48
-
-
Save kambala-decapitator/695f663c3e508ef70da29c923c0b7261 to your computer and use it in GitHub Desktop.
Small macOS CLI program to list filesystem items
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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