commit e6f0a12ef0d61ca15c95942c8012d9a39e7488f4 Author: Kelvin Sherlock Date: Sun Jul 14 18:15:56 2013 -0400 MPW Tools (split from MPW) diff --git a/Duplicate.c b/Duplicate.c new file mode 100644 index 0000000..aecc113 --- /dev/null +++ b/Duplicate.c @@ -0,0 +1,346 @@ +#include +#include +#include +#include +#include +#include + +/* +Duplicate # duplicate files and directories +Duplicate [-y | -n | -c] [-p] [-d | -r] name... target > progress + -y # overwrite target files (avoids dialog) + -n # don't overwrite target files (avoids dialog) + -c # cancel if conflict occurs (avoids dialog) + -p # write progress information to diagnostics + -d # duplicate data fork only + -r # duplicate resource fork only + -rs # resolve leaf aliases in the source path(s) + -rt # resolve leaf aliases in the target path + -f # preserve Finder icon locations + +*/ +// todo -- support src1 src2 ... dest/ + + +char *c2p(const char *cp) +{ + int length; + char *p; + + if (!cp) return NULL; + length = strlen(cp); + if (length > 255) + { + fprintf(stderr, "Error: Pathname is too long.\n"); + exit(1); + return NULL; + } + + p = malloc(length + 2); // + 2 for \0 and length. + if (!p) + { + fprintf(stderr, "Error: unable to allocate memory.\n"); + exit(1); + return NULL; + } + + p[0] = length; + memcpy(p + 1, cp, length + 1); + return p; +} + + +void help(void) +{ + fprintf(stdout, "Usage: Duplicate [-y | -n | -c] [-p] [-d |-r] source destination\n"); +} + +int getopt(int *opts, int argc, char **argv) +{ + int i = 0; + + for (i = 1; i < argc; ++i) + { + char *str = argv[i]; + char c = str[0]; + + if (c != '-') return i; + ++str; + + // -- to terminate + if (str[0] == '-' && str[1] == 0) + return i + 1; + + // skip -rt, -rs + if (str[0] == 'r' && str[1] == 's' && str[2] == 0) + continue; + + if (str[0] == 'r' && str[1] == 't' && str[2] == 0) + continue; + + for (; *str; ++str) + { + c = *str; + switch(c) + { + case 'r': + case 'd': + opts['r' - 'a'] = 0; + opts['d' - 'a'] = 0; + opts[c - 'a'] = 1; + break; + case 'y': + case 'n': + case 'c': + opts['y' - 'a'] = 0; + opts['n' - 'a'] = 0; + opts['c' - 'a'] = 0; + opts[c - 'a'] = 1; + break; + case 'p': + opts[c - 'a'] = 1; + break; + case 'h': + help(); + exit(0); + break; + default: + fprintf(stderr, "Duplicate - Invalid flag: \"%c\"\n", c); + exit(1); + break; + + } + } + } + return i; +} + +int copyFork(const char *src, const char *dest, unsigned fork) +{ + static char buffer[4096]; + + int rfd, wfd; + + int rv; + + fork = fork ? O_RSRC : 0; + + rfd = open(src, O_RDONLY | O_BINARY | fork); + if (rfd < 0) + { + fprintf(stderr, "Error opening %s: %s\n", src, strerror(errno)); + return -1; + } + + // no 3rd parameter to open. + wfd = open(dest, O_WRONLY | O_BINARY | O_CREAT |O_TRUNC| fork); + if (wfd < 0) + { + fprintf(stderr, "Error opening %s: %s\n", dest, strerror(errno)); + close(rfd); + return -1; + } + + rv = -1; + for (;;) + { + ssize_t rsize; + ssize_t wsize; + + rsize = read(rfd, buffer, sizeof(buffer)); + if (rsize == 0) + { + rv = 0; + break; + } + if (rsize < 0) + { + if (errno == EINTR) continue; + fprintf(stderr, "Error reading %s: %s\n", src, strerror(errno)); + break; + } + + wsize = write(wfd, buffer, rsize); + if (wsize != rsize) + { + fprintf(stderr, "Error writing %s: %s\n", dest, strerror(errno)); + break; + } + } + + close(rfd); + close(wfd); + return rv; +} + +int copyFinderInfo(const char *src, const char *dest) +{ + FInfo finderInfo; + OSErr status; + + char *psrc; + char *pdest; + + psrc = c2p(src); + pdest = c2p(dest); + if (!psrc || !pdest) return -1; + + // getfinfo/setfinfo seem to have bugs. + + memset(&finderInfo, 0, sizeof(finderInfo)); + + status = GetFInfo((unsigned char *)psrc, 0, &finderInfo); + if (status == 0) + { + status = SetFInfo((unsigned char *)pdest, 0, &finderInfo); + } + free(psrc); + free(pdest); + + if (status) return -1; + + return 0; +} + +int createFile(const char *src, const char *dest) +{ + FInfo finderInfo; + OSErr status; + + char *psrc; + char *pdest; + + psrc = c2p(src); + pdest = c2p(dest); + if (!psrc || !pdest) return -1; + + memset(&finderInfo, 0, sizeof(finderInfo)); + + status = GetFInfo((unsigned char *)psrc, 0, &finderInfo); + + status = Create((unsigned char *)pdest, 0, finderInfo.fdCreator, finderInfo.fdType); + free(psrc); + free(pdest); + + if (status) return -1; + return 0; +} + +// -1 - error. +// 0 - no file +// 1 - regular file +// 2 - directory. +int mode(const char *path) +{ + char *pname; + CInfoPBRec rec; + OSErr status; + + memset(&rec, 0, sizeof(rec)); + + pname = c2p(path); + if (!pname) return -1; + + rec.hFileInfo.ioNamePtr = (unsigned char *)pname; + status = PBGetCatInfo(&rec, false); + free(pname); + + if (status) return 0; + if (rec.hFileInfo.ioFlAttrib & kioFlAttribDirMask) + return 2; + + return 1; +} + +int main(int argc, char **argv) +{ + int opts[26]; + int optind; + int ok; + char *src; + char *dest; + int m; + + memset(opts, 0, sizeof(opts)); + + opts['n' - 'a'] = 1; + optind = getopt(opts, argc, argv); + argc -= optind; + argv += optind; + + if (argc != 2) + { + help(); + exit(1); + } + + src = argv[0]; + dest = argv[1]; + + // 1. check if src exists + // 2. check if dest exists + + // 3. copy data fork unless -r + // 4. copy resource fork unless -d + // 5. copy finder info. + + ok = 0; + // -n - do not overwrite + // -c - cancel if conflict + + m = mode(dest); + if (m == 2) + { + fprintf(stderr, "Error: directory destination is not yet supported.\n"); + exit(1); + } + if (m == 0 && opts['r' - 'a']) + { + // workaround to create the file if + // only copying the resource fork. + + // TODO -- call Create(name, 0, creator, filetype) + + if (opts['p' - 'a']) + printf("Creating file %s\n", dest); + ok = createFile(src, dest); + if (ok < 0) + { + fprintf(stderr, "Error creating %s\n", dest); + exit(1); + } + } + + if (m == 1) + { + // todo -- should this check at the file level or at the fork level? + // seems to check at the file level. + + // file exists. + if (opts['c' - 'a'] || opts['n' - 'a']) + { + if (opts['p' - 'a']) + { + printf("File exists - nothing done.\n"); + } + exit(0); + } + } + + + if (opts['r' - 'a'] == 0) + { + if (opts['p' - 'a']) + printf("Copying Data Fork.\n"); + ok = copyFork(src, dest, 0); + } + + if (opts['d' - 'a'] == 0) + { + if (opts['p' - 'a']) + printf("Copying Resource Fork.\n"); + ok = copyFork(src, dest, 1); + } + + return ok; +} \ No newline at end of file diff --git a/GetEnv.c b/GetEnv.c new file mode 100644 index 0000000..ade361a --- /dev/null +++ b/GetEnv.c @@ -0,0 +1,28 @@ +/* + * + * GetEnv variable + * read an mpw variable. + * eg: CLibraries=`mpw GetEnv CLibraries` + * (makefile) CLibraries = $(shell mpw GetEnv CLibraries) + * flags to do name = value? + */ + +#include +#include + +int main(int argc, char **argv) +{ + char *value; + char *name; + + if (argc != 2) + { + return 1; + } + + name = argv[1]; + value = getenv(name); + // value == null if not defined. + if (value) puts(value); + return 0; +} diff --git a/Help.c b/Help.c new file mode 100644 index 0000000..a346ce5 --- /dev/null +++ b/Help.c @@ -0,0 +1 @@ +/* * MPW Help utility. * * help topic * does a cat of $ShellDirectory:Help:topic */ /* * MPW 3.2 * * C Help.c -o Help.c.o -r {SymOptions} * * Link {SymOptions} -w -c 'MPS ' -t MPST Help.c.o ∂ * -sn STDIO=Main ∂ * -sn INTENV=Main ∂ * -sn %A5Init=Main ∂ * "{Libraries}"Stubs.o ∂ * "{CLibraries}"StdCLib.o ∂ * "{Libraries}"Interface.o ∂ * "{Libraries}"Runtime.o ∂ * "{Libraries}"ToolLibs.o ∂ * -o Help * */ #include #include #include char *base(char *str) { char *rv = str; char *tmp = str; if (!str) return str; for (tmp = str; *tmp; ++tmp) { char c = *tmp; if (c == ':' || c == '/') rv = tmp + 1; } return rv; } void help(char *root, char *topic) { int l; char *path; FILE *fp; // todo -- check if they're being stupid or malicious and // handle / or : chars. topic = base(topic); if (!topic || !*topic) { return; } l = strlen(root) + strlen("Help:") + strlen(topic) + 1; path = malloc(1); if (!path) { fprintf(stderr, "### Help - Memory allocation failure.\n"); return; } sprintf(path, "%sHelp:%s", root, topic); fp = fopen(path, "r"); free(path); if (!fp) { fprintf(stderr, "### Help - \"%s\" was not found.\n", topic); return; } for(;;) { char buffer[512]; int count; count = fread(buffer, 1, sizeof(buffer), fp); if (count == 0) break; fwrite(buffer, 1, count, stdout); } fwrite("\r", 1, 1, stdout); fclose(fp); } int main(int argc, char **argv) { char *root; root = getenv("ShellDirectory"); if (!root || !*root) { fprintf(stderr, "### Help - $ShellDirectory is not defined.\n"); return 1; } if (argc == 1) { help(root, "MPW"); } else { int i; for (i = 1; i < argc; ++i) { help(root, argv[i]); } } return 0; } \ No newline at end of file diff --git a/SetFile.c b/SetFile.c new file mode 100644 index 0000000..e0298ce --- /dev/null +++ b/SetFile.c @@ -0,0 +1,151 @@ + +#include +#include +#include +#include + +#include +#include + +#include "SetFile-flags.h" + + + +int tox(char c) +{ + c |= 0x20; + if (c >='0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + return 0; +} + +int hex(const char *in, char *out, int length) +{ + int i; + for (i = 0; i < length; ++i) + { + int tmp = 0; + char c; + + c = *in++; + if (!isxdigit(c)) return -1; + tmp |= tox(c) << 4; + + c = *in++; + if (!isxdigit(c)) return -1; + tmp |= tox(c); + + *out++ = tmp; + } + return 0; +} + +// convert the file/creators... +// format: +// 0x \xdigit{8} +// $ \xdigit{8} +// 4-cc code +int checkcode(const char *in, char *out) +{ + int length; + + length = strlen(in); + + if (length == 4) + { + // 4 cc code. + int i; + for (i = 0; i < 4; ++i) + out[i] = in[i]; + return 0; + } + + if (length == 9 && in[0] == '$') + return hex(in + 1, out, 4); + + if (length ==10 && in[0] == '0' && in[1] == 'x') + return hex(in + 2, out, 4); + + return -1; +} + +int main(int argc, char **argv) +{ + + FInfo newFI; + int optind; + int ok; + int i; + + + optind = FlagsParse(argc, argv); + + argc -= optind; + argv += optind; + + if (argc == 0) + { + FlagsHelp(); + return 0; + } + + + memset(&newFI, 0, sizeof(newFI)); + + if (!flags._t && !flags._c) return 0; + + if (flags._t) + { + ok = checkcode(flags._t, (char *)&newFI.fdType); + if (ok < 0) + { + fprintf(stderr, "SetFile: invalid file type: `%s`\n", flags._t); + exit(1); + } + } + + if (flags._c) + { + ok = checkcode(flags._c, (char *)&newFI.fdCreator); + if (ok < 0) + { + fprintf(stderr, "SetFile: invalid creator type: `%s`\n", flags._c); + exit(1); + } + } + + for (i = 0; i < argc; ++i) + { + FInfo fi; + char buffer[256]; + char *cp; + int l; + + cp = argv[i]; + l = strlen(cp); + if (l > 255) + { + fprintf(stderr, "SetFile: %s: file name too long.\n", cp); + continue; + } + + buffer[0] = l; + memcpy(buffer + 1, cp, l); + + memset(&fi, 0, sizeof(fi)); + + ok = GetFInfo((unsigned char *)buffer, 0, &fi); + + if (flags._t) fi.fdType = newFI.fdType; + if (flags._c) fi.fdCreator = newFI.fdCreator; + + ok = SetFInfo((unsigned char *)buffer, 0, &fi); + if (ok != 0) + { + fprintf(stderr, "SetFile: %s: unable to set finder info: %d\n", cp, ok); + } + } + + exit(0); + return 0; +} \ No newline at end of file diff --git a/flags.rb b/flags.rb new file mode 100644 index 0000000..062c0a0 --- /dev/null +++ b/flags.rb @@ -0,0 +1,314 @@ +#!/usr/bin/env ruby -w + +# process the flags.yaml file +# and generate a flags.h and flags.c file. +# + +# +# todo -- support for long-options (--longoption, --longoption=value, etc) +# +# + +require 'erb' +require 'yaml' + +header_preamble = <' => 'gt', + '<' => 'lt', + ',' => 'comma', + '.' => 'period', + '/' => 'forward_slash', + '\\' => 'back_slash', + '?' => 'question', + '|' => 'pipe', + '~' => 'tilde', + '`' => 'grave', + '!' => 'bang', + '@' => 'at', + '#' => 'hash', + '$' => 'dollar', + '%' => 'percent', + '^' => 'caret', + '&' => 'ampersand', + '*' => 'star', + '(' => 'left_paren', + ')' => 'right_paren', + '-' => 'minus', + '+' => 'plus', + '=' => 'equal', + '[' => 'left_bracket', + ']' => 'right_bracket', + '{' => 'left_brace', + '}' => 'right_brace', + ':' => 'colon', + ';' => 'semi_colon', + '\'' => 'apostrophe', + '"' => 'quote' + } + + def initialize(hash) + + @char = hash['char'].to_s + @argument = hash['argument'] || false + + @flag_name = hash['flag_name'] + @flag_name = @flag_name.to_s if @flag_name + + @xor = hash['xor'] || [] + @xor = case @xor + when Array + @xor + when Integer, String + [ @xor ] + else + raise "Invalid xor type: #{@xor}" + end + + @xor.map! { |x| x.to_s } + end + + attr_reader :char, :xor, :argument + + def flag_name + return @flag_name if @flag_name + return self.class.flag_name(@char) + end + + def self.flag_name(char) + return '_' + @@map[char] if @@map[char] + return '_' + char + end + + +end + +# better ARGF. +def argf_each + + if ARGV.count > 0 + + ARGV.each {|file| + + File.open(file, "r") {|io| + yield file, io + } + } + + else + yield nil, $stdin + end + +end + + +def escape_cstr(x) + + # escape a c string + + x.gsub(/([\\"])/, "\\\\1") +end + + +code = ERB.new(DATA.read(), 0, "%<>") + +argf_each {|filename, file| + + + data = YAML.load(file) + + help = data['help'] + options = data['options'] + + # options is an array of items which may be hashes, strings, or numbers. + # normalize them. + + options = options.map {|opt| + + opt = case opt + when String, Integer + { 'char' => opt } + when Hash + # {'o' => { ... }} + # or + # {'char' => , ... } + if opt['char'] + opt + else + opt = opt.first + opt[1].merge({ 'char' => opt[0] }) + end + else + raise "Unexpected data type: #{opt}" + end + + Option.new(opt) + } + + #data[options] = options + # check for help? + + basename = filename + basename = $1 if filename && filename =~ /^(.*)\./ + + b = binding # bind help, options for ERB. + + io = basename ? File.open(basename + ".c", "w") : $stdout + io.write(code.result(b)) + + io.close unless io == $stdout + + + io = basename ? File.open(basename + ".h", "w") : $stdout + io.write(header_preamble) + # two passes - one with arguments, one without. + options.each {|opt| + if opt.argument + io.printf(" char *%s;\n", opt.flag_name) + end + } + io.puts() + options.each {|opt| + if !opt.argument + io.printf(" unsigned %s:1;\n", opt.flag_name) + end + } + io.puts + + io.write(header_postamble) + io.close unless io == $stdout + + +# #puts options.to_yaml +# puts code.result(binding()) + +} + + +__END__ + +#ifdef __ORCAC__ +#pragma optimize 79 +#pragma noroot +#endif + +#include +#include +#include + +#include "<%= basename + '.h' %>" + +void FlagsHelp(void) +{ +% help.each do |h| + fputs("<%= escape_cstr(h) %>\n", stdout); +% end + fputs("\n", stdout); + exit(0); +} + +struct Flags flags; +int FlagsParse(int argc, char **argv) +{ + char *cp; + char c; + int i; + int j; + + memset(&flags, 0, sizeof(flags)); + + for (i = 1; i < argc; ++i) + { + cp = argv[i]; + c = cp[0]; + + if (c != '-') + return i; + + // -- = end of options. + if (cp[1] == '-' && cp[2] == 0) + return i + 1; + + // now scan all the flags in the string... + for (j = 1; ; ++j) + { + int skip = 0; + + c = cp[j]; + if (c == 0) break; + + switch (c) + { +% if help && !options.find_index {|x| x.char == 'h' } + case 'h': + FlagsHelp(); + break; +% end +% # +% options.each do |opt| + case '<%= escape_cstr(opt.char) %>': +% # check for an argument. +% flag_name = 'flags.' + opt.flag_name +% # +% if opt.argument + // -xarg or -x arg + skip = 1; + if (cp[j + 1]) + { + <%= flag_name %> = cp + j + 1; + } + else + { + if (++i >= argc) + { + fputs("option requires an argument -- <%= opt.char %>\n", stderr); + return -1; + } + <%= flag_name %> = argv[i]; + } +% else # no argument. + <%= flag_name %> = 1; +% end # if no argument. +% # +% # unset any exclusive or values +% opt.xor.each do |xor_opt| + flags.<%= Option.flag_name(xor_opt) %> = 0; +%end + break; +% end # options.each + + default: + fprintf(stderr, "illegal option -- %c\n", c); + return -1; + } + + if (skip) break; + } + } + + return i; +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..af2c33f --- /dev/null +++ b/makefile @@ -0,0 +1,61 @@ +# makefile + +Libraries=~/mpw/Libraries/Libraries +CLibraries=~/mpw/Libraries/CLibraries + +# MPW 3.2 +LIBS = \ + $(Libraries)/Stubs.o \ + $(CLibraries)/StdCLib.o \ + $(Libraries)/Interface.o \ + $(Libraries)/Runtime.o \ + $(Libraries)/ToolLibs.o + +LDFLAGS = -w -c 'MPS ' -t MPST \ + -sn STDIO=Main -sn INTENV=Main -sn %A5Init=Main + +# MPW 3.5 + +# LIBS = \ +# $(CLibraries)/StdCLib.o \ +# $(Libraries)/Stubs.o \ +# $(Libraries)/IntEnv.o \ +# $(Libraries)/MacRuntime.o \ +# $(Libraries)/Interface.o \ +# $(Libraries)/ToolLibs.o + +# LDFLAGS = -d -c 'MPS ' -t MPST + +all: Help GetEnv Duplicate SetFile + +clean: + rm -f *.c.o + rm -f Help GetEnv Duplicate SetFile + +GetEnv: GetEnv.c.o + mpw Link $(LDFLAGS) -o $@ $^ $(LIBS) + +Help: Help.c.o + mpw Link $(LDFLAGS) -o $@ $^ $(LIBS) + + +Duplicate: Duplicate.c.o + mpw Link $(LDFLAGS) -o $@ $^ $(LIBS) + + +SetFile: SetFile.c.o SetFile-flags.c.o + mpw Link $(LDFLAGS) -o $@ $^ $(LIBS) + + +#SetFile.c : SetFile.rl +# ragel -G2 -p -m -o $@ $< + + +%.c.o : %.c + mpw SC -p $< -o $@ + +# GetEnv.c.o : GetEnv.c +# mpw SC -p GetEnv.c -o $@ + +# Help.c.o : Help.c +# mpw SC -p Help.c -o $@ \ No newline at end of file diff --git a/split-help.rb b/split-help.rb new file mode 100644 index 0000000..ea1def3 --- /dev/null +++ b/split-help.rb @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby -w + + +# +# format +# - +# name # comment +# + +file = nil +state = nil +#ARGF.binmode +#ARGF.set_encoding("BINARY") +ARGF.each { |line| + + line.chomp! + + case state + when nil + if line == '-' + state = :name + end + + when :name + if line.match(/^([A-Za-z0-F]+)\s?#?/) + state = :data + filename = $1 + file = File::new("Help.Files/#{filename}", "w") + #file.set_encoding("BINARY") + end + + when :data + if line == '-' + state = :name + file = nil + else + file.puts(line) + end + + + + end + +}