/*
spacetab - A text file converter for tabs, spaces and line-endings
By default, it converts every file in the current directory with the given settings
WARNING: this tool will corrupt any binary file it encounters
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#define NO_SC 0
#define TO_TABS 1
#define TO_SPACES 2
#define NO_LC 0
#define TO_DOS 1
#define TO_UNIX 2
#define TYPE_FILE 0
#define TYPE_DIR 1
#define TYPE_OTHER 2
char *absolute_path(char *in, int fwd) {
if (!in || strlen(in) < 1)
return NULL;
int len = strlen(in);
// my definition of an absolute path
if (in[0] == '/' || in[0] == '\\' ||
(len >= 2 && in[1] == ':'))
{
return strdup(in);
}
char cwd[256];
if (getcwd(cwd, 256) == NULL)
return NULL;
int path_len = strlen(cwd) + 1 + strlen(in);
char *path = malloc(path_len + 1);
sprintf(path, "%s/%s", cwd, in);
int i;
for (i = 0; i < path_len; i++) {
if (fwd && path[i] == '\\')
path[i] = '/';
else if (!fwd && path[i] == '/')
path[i] = '\\';
}
return path;
}
typedef unsigned char u8;
typedef struct {
char *name;
char *fullName;
int type;
int size;
} Entry;
typedef struct {
u8 *data;
int size;
} Buffer;
void appendSection(Buffer *dst, Buffer *src, int start, int end) {
int len = end - start + 1;
if (len < 1)
return;
dst->data = realloc(dst->data, dst->size + len);
memcpy(dst->data + dst->size, src->data + start, len);
dst->size += len;
}
void appendSpan(Buffer *b, char c, int len) {
b->data = realloc(b->data, b->size + len);
memset(b->data + b->size, c, len);
b->size += len;
}
void appendString(Buffer *b, char *str) {
int len = strlen(str);
b->data = realloc(b->data, b->size + len);
memcpy(b->data + b->size, str, len);
b->size += len;
}
void convertFile(Entry *e, int spt, int spMode, int lnMode) {
if (!e || e->type != TYPE_FILE || e->size <= 0)
return;
FILE *f = fopen(e->fullName, "rb");
if (!f)
return;
Buffer in = {NULL, e->size};
Buffer out = {NULL, 0};
in.data = malloc(in.size);
fread(in.data, 1, in.size, f);
fclose(f);
int start = 0;
int col = 0, line = 0;
int leadWh = 1, spCon = 0;
int i;
for (i = 0; i < in.size; i++) {
if (in.data[i] != ' ')
spCon = 0;
if (leadWh && !(in.data[i] == ' ' || in.data[i] == '\t'))
leadWh = 0;
if (spMode == TO_TABS) {
if (in.data[i] == ' ' && leadWh)
spCon++;
if (spCon == spt) {
appendSection(&out, &in, start, i - spCon);
appendSpan(&out, '\t', 1);
start = i + 1;
spCon = 0;
}
}
else if (spMode == TO_SPACES) {
if (in.data[i] == '\t') {
appendSection(&out, &in, start, i - 1);
appendSpan(&out, ' ', spt);
start = i + 1;
}
}
int wasCr = i > 0 && in.data[i-1] == '\r';
if (lnMode == TO_DOS) {
if (in.data[i] == '\n' && !wasCr) {
appendSection(&out, &in, start, i - 1);
appendString(&out, "\r\n");
start = i + 1;
}
}
else if (lnMode == TO_UNIX) {
if (in.data[i] == '\n' && wasCr) {
appendSection(&out, &in, start, i - 2);
appendString(&out, "\n");
start = i + 1;
}
}
col++;
if (in.data[i] == '\n') {
line++;
col = 0;
leadWh = 1;
}
}
appendSection(&out, &in, start, in.size - 1);
f = fopen(e->fullName, "wb");
if (f) {
fwrite(out.data, 1, out.size, f);
fclose(f);
}
free(in.data);
free(out.data);
}
int createEntry(Entry *e, char *dirName, char *name) {
if (!e || !dirName || !name)
return -1;
char *full = malloc(strlen(dirName) + 1 + strlen(name) + 1);
sprintf(full, "%s/%s", dirName, name);
struct stat info = {0};
int res = stat(full, &info);
if (res < 0) {
free(full);
return -2;
}
e->name = strdup(name);
e->fullName = full;
e->type = TYPE_OTHER;
e->size = 0;
if (info.st_mode & S_IFREG) {
e->type = TYPE_FILE;
e->size = info.st_size;
}
if (info.st_mode & S_IFDIR)
e->type = TYPE_DIR;
return 0;
}
void iterate(char *directory, int recursive, int spt, int spMode, int lnMode) {
DIR *handle = opendir(directory);
struct dirent *cur;
Entry *list = malloc(sizeof(Entry));
int n_items = 0;
while ((cur = readdir(handle)) != NULL) {
if (!strcmp(cur->d_name, ".") || !strcmp(cur->d_name, ".."))
continue;
if (createEntry(&list[n_items], directory, cur->d_name) < 0)
continue;
n_items++;
list = realloc(list, (n_items + 1) * sizeof(Entry));
}
int i;
for (i = 0; i < n_items; i++) {
if (list[i].type == TYPE_FILE) {
convertFile(&list[i], spt, spMode, lnMode);
}
else if (recursive && list[i].type == TYPE_DIR) {
int nextLen = strlen(directory) + 1 + strlen(list[i].name);
char *nextDir = malloc(nextLen + 1);
sprintf(nextDir, "%s/%s", directory, list[i].name);
iterate(nextDir, 1, spt, spMode, lnMode);
free(nextDir);
}
free(list[i].name);
free(list[i].fullName);
}
free(list);
}
void printHelp() {
printf("Space-Tab Converter:\n"
"For converting spaces to tabs and vice versa\n"
"Can optionally convert line-endings from DOS to Unix and vice versa\n"
"Options:\n"
" -h:\n"
" Print this menu\n"
" -R:\n"
" Recursively locates files to convert\n"
" -d <directory>:\n"
" Specifies the directory to start converting files from\n"
" -f <filename>:\n"
" Specifies a single file in the given directory to convert,\n"
" instead of every file inside that directory. Cancels the -R flag.\n"
" -n <length>:\n"
" Specifies the number of spaces per tab (default: 4)\n"
" -t:\n"
" Converts leading spaces to tabs (you want this ;))\n"
" -s:\n"
" Converts tabs to spaces (believe me, you don't want this :P)\n"
" -w:\n"
" Converts all Unix line-endings to DOS\n"
" -u:\n"
" Converts all DOS line-endings to Unix\n");
}
int main(int argc, char **argv) {
int recursive = 0;
char *directory = ".";
char *fileName = NULL;
int spacesPerTab = 4;
int spaceMode = NO_SC;
int lineMode = NO_LC;
int i;
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-')
continue;
switch (argv[i][1]) {
case 'h':
printHelp();
return 0;
case 'R':
recursive = 1;
break;
case 'd':
if (i >= argc-1)
break;
directory = argv[++i];
break;
case 'f':
if (i >= argc-1)
break;
fileName = argv[++i];
break;
case 'n':
if (i >= argc-1)
break;
spacesPerTab = atoi(argv[++i]);
break;
case 't':
spaceMode = TO_TABS;
break;
case 's':
spaceMode = TO_SPACES;
break;
case 'w':
lineMode = TO_DOS;
break;
case 'u':
lineMode = TO_UNIX;
break;
}
}
if (spaceMode == NO_SC && lineMode == NO_LC) {
printf("No conversion specified\n"
"For help, type\n"
"%s -h\n", argv[0]);
return 0;
}
char *path = absolute_path(directory, 1);
if (fileName) {
Entry file = {0};
if (createEntry(&file, path, fileName) >= 0) {
convertFile(&file, spacesPerTab, spaceMode, lineMode);
free(file.name);
free(file.fullName);
}
}
else {
iterate(path, recursive, spacesPerTab, spaceMode, lineMode);
}
free(path);
return 0;
}