#!/bin/bash # File: new_c.sh # Date: 27 April 2012 # Version: 2.3a 10 July 2012 # Author: Jason Charney (jrcharneyATgmailDOTcom) # LICENSE: MIT LICENSE (Acknowledge me as the orignal creator, modify if you want but let me know, free to remix and free to download.) # Info: Create a new .h file or .c/.cpp file # This file is designed to elimiate repetitive tasks # TODO: Get data from UML files? (More of a task for a future C/C++ program) # TODO: Generate archives? # TODO: Generate makefiles? # TODO: Compile and link? # TODO: Objective-C # TODO: Objective-C++ # TODO: Debugging # TODO: Set up a procedure to set up random number generation (includes time.h and math.h?) # Notes: # If you download this, keep my name on it. I worked hard on this file. # You can make changes to this file if you want, but let me know about it. # Don't sell this file. If you plan on making money off of it, let me know and please pay me fairly for it. # If this file isn't executable, try chmod u+x new.sh # (c) 2012 All rights reserved. # -------------- OPTIONS YOU CAN MODIFY HERE ----------------- a="Jason Charney (jrcharneyATgmailDOTcom)" # set the author to your name # -------------- DON'T TOUCH ANYTHING BELOW THIS LINE UNLESS YOU KNOW WHAT TO DO ----------- # Func: usage # Info: Help info # TODO: Should this data go in a separate file? # Status: Make sure this is up to date later. function usage(){ cat << EOM new_c - The fastest way to generate C/C++ files CREATED BY: Jason Charney (jrcharneyATgmailDOTcom) USAGE new_c [OPTIONS] new_c -w [PATH/]filename.c [main|h] [INCLUDES] new_c -w [PATH/]filename.cpp [main|h] [INCLUDES] new_c -w [PATH/]filename.h [c|cpp] [class] [INCLUDES] In this form, classes can be created new_c -o [PATH/]filename.EXT Compile and Link one file. new_c -c [PATH/]filename.EXT Compile one file. new_c -a [PATH/]filename.a View archive new_c -a [PATH/]filename.a PATH/filename.o [...] Create or update an archive with modules new_c -l [PATH/]filename.[ao] [...] Link program new_c -d [PATH/]program [args] Debug program OPTIONS -h --help Call this help message -w --write,--compose Create a new file if it does not exists -Iinclude Add system include files to the file () -iinclude Add local include files to the header ("myfile.h") -nnamepace Add a "using namespace" command. (Common in C++) -G --OpenGL Add OpenGL includes (FUTURE) -c --compile Compile a .c/.cpp file into a .o file -a --archive Archive a set of .o files into an .a file -t --list ...list what is in the archive (add a path/filename to see specifc items) -r --replace ...insert files into an archive with replacement if file exists -u --update ...insert only the files that are newer than existing members -d --remove,--delete obj ... delete a specific item. An argument is required. --update -x --extract ...extract with is in the archive (add a path/filename to extract specific items) -v --verbose -o ArcName.a ...with this archive name If you need something specific, try looking at "man ar" -l --link Link (files are separated by commas) -A archive[,archive] ...with archives -I include[,include] ...with includes -L library[,library] ...with libraries/shared objects -G --OpenGL ...with OpenGL attibutes -o PrgmName ...with this program name -o or -O Compile and Link (ideal for single .c/.cpp files) -o PrgmName ...With this program name HOW IT WORKS If the PATH does not exist, new_c creates the PATH in the process. Create a new file ending in .c, .cpp, or .h. If the file ends with .c or .cpp, use the second argument to define it as as the file with the main function using "main" or define it as the source file for a module using "h". If if the file ends with .h, use "c" or "cpp" to create a corresponding .c or .cpp file for modules. You'll still need to define the function in each file. This script just takes care of the repetitive tasks of writing headers and general text commonly used in C/C++ programming. EOM exit 0 } # Func: egress # Info: Exit on error with message # Status: This function is fine at the moment. Don't mess with it! function egress(){ echo "$1" 2>&1 # forward stderr to stdout exit 1 # exit with error status } # Func: trailblazer # Info: Define the path of one level or more if needed. # Status: This function is fine. Don't mess with it! function trailblazer(){ if [ ! -d $1 ]; then trailblazer ${1%/*} # recursive mkdir $1 fi } # Func: compose_header # Info: compose the preamble of a file including the file name, date, and the person who wrote it. # everything else will bee needed to written in the file itself # Status: This function is fine at the moment. DON'T MESS WITH IT! function compose_header(){ F=$1 # File name d=$(date "+%e %b %Y") # File date # Using <<- will cause bash to ignore leading tabs but not spaces in a here script cat > $F << EOF /* File: $F * Date: $d * Author: $a * Info: */ EOF } # Func: append_line # Info: Append a line of code to a file # Note: This function exists to make alot of the code use in here to look pretty # Note: append_line only works for one line only and the line can not be blank. # Note: You can escape quotes but not newline characters. # THIS MAY BE A VERSION 2.2 feature # Args: # $1 = $F = the file to append code to # $2 = $C = the code to append to the file, It should probably be in quotes to preserve spaces. # STATUS: pending. This is just an idea for the next version. function append_line(){ cat >> $1 << EOL $2 EOL } # Func: compose_includes # Info: add any include files # TODO: comma-separated arguments? Probably not. # TODO: What happens if a C++ file uses a C library? (Should I add "c" to the begining of the file name and strip out the ".h"?) # Status: BETA. THIS FUNCTION SHOULD BE GOOD TO GO! # TODO: cfile includes are -I, -i, and -n, hfile includes should be -H -h, and -m # TODO: What if I used a different function to write data to files? (IMPORTANT TO LOOK INTO) # TODO: For local includes, check to make sure .h is at the end of the file. # TODO: $F should be deleted if any errors occur! # NOTE: Paths can include paths # NOTE: These should be uses as such -Xfile[.h] where -X is the command, and file[.h] is the include file. function compose_includes(){ F=$1 # filename that these includes will be added to. shift # increment to the next argument while [ $# -gt 0 ]; do # while loop executes so as long as there are arguments left to execute inc=${1#-[Iin]} # Strip out the -I, -i, or -n case ${1%${inc}} in # Use the -I, -i, or -n "-I") append_line $F "#include <$inc>" ;; # System Includes "-i") append_line $F "#include \"$inc\"" ;; # Local includes "-n") append_line $F "using namespace $inc;" ;; # Include a namespace (C++) (generally std or ios) *) egress "$0 Invaid inclusion option. Aborting." ;; # This should NEVER happen anyway esac shift # Incrememnt to the next argument done # end of while block } # Func: compose_class # Info: compose a class declaration in a .h file and define the functions in the .c/.cpp file # Note: Classes will likely be individual and be named after the file they are contained in. # TODO: # [ ] Capitalize the first letter of the file name after stripping out the path when using it as a class name. # STATUS: MAJOR WISHLIST ITEM. SOON! This should be a version 3 feature. # Func: compose_body # Info: compose the body of a main file or a module file # THIS FUNCTION IS FINE! DON'T MESS WITH IT! # TODO: Class creation function compose_body(){ t=${1##*.} # File extension F=$1 # File shift case $t in c|cpp) # called to create the main function # TODO: HEADERS SHOULD BE INSERTED HERE IF THEY ARE NEEDED! if [ $# -gt 0 ]; then compose_includes $F $@ fi # TODO: Shouldn't this cat block be called when "-main$F" is used? cat >> $F << EOF int main( int argc, char* argv[] ) { return 0; }; EOF ;; h) # called to create module headers # TODO: What if I wanted to create a class of the same name in this file? ("-class$F"?) # doing this special procedure will prevent multipule inclusion of header files S=${F##*/} # strip out the path S=${S^^*} # Make everything uppercase S=${S//\./_} # Replace periods with underscores (correct this if wrong) cat >> $F << EOF #ifndef $S #define $S EOF # TODO: HEADERS SHOULD BE INSERTED HERE IF THEY ARE NEEDED! if [ $# -gt 0 ]; then compose_includes $F $@ fi cat >> $F << EOF #endif // $S EOF ;; *) egress "How did you get here?" ;; # This should not happen esac } # Func: include_header # Info: includes a header file into the body of a .c/.cpp file # NOTE: The path is stripped out because the .c/.cpp file should be in the same directory as the .h file. # NOTE: This is for LOCAL HEADERS # TODO: [Y in the next function] Is this function still used? If not delete it. # [ ] Can it be replaced with compose_includes? function include_header(){ # cfile=$1 hfile=$2 cat >> $1 << EOF #include "${2##*/}" EOF } # Create a file based on the extension # NOTE: Be sure trailblazer runs first! # TODO: Rewrite this to include the shift command # TODO: include add_include functions # TODO: include inclusion definition needs to be specific in case headers don't need includes # Consolidate cases that are similar. # STATUS: Review and modify. (I don't think I finished this part.) function generator(){ t=${1##*.} # File extension case $t in # TODO: Would it be resonable to consolidate the c and cpp cases? c|cpp) # ** File ends with .c or .cpp ** # TODO: set up $2 to define if file is a main, module, or user-defined file cfile=$1 compose_header $cfile shift # $2 becomes $1 if [[ $1 ]]; then case $1 in main) shift # MAJOR TODO! Add SHIFTS to place that need them! if [ $# -gt 0 ]; then compose_includes $cfile $@ # append any include files TODO: What if I used include_header instead? fi compose_body $cfile # append the main function ;; h) shift hfile=${cfile/%$t/h} # set the header file name TODO: Hopefully this works compose_header $hfile # create the complementary header file # compose_includes $hfile $@ # append any include files compose_body $hfile # there is a special procedure for creating a header file for modules include_header $cfile $hfile # In $cfile, include the $hfile TODO: See above ;; *) # TODO: make main a default option # Chances are, you'll forget to use "main" or "h". For those who forget, here's this. if [ $# -gt 0 ]; then compose_includes $cfile $@ # append any include files fi compose_body $cfile # append the main function ;; # else do nothing esac # case $1 fi # if $1 ;; h) # ** File ends with .h ** hfile=$1 compose_header $hfile # compose_includes $hfile $@ # NOTICE: If you reinable this line, you might want to put SHIFT before it. shift if [[ $1 ]]; then # if a second argument "c" or "cpp" exists t=$1 # Most other cases, t=${1##*.} (a file extension) case $1 in c|cpp) # Consolidated the c and cpp cases due to their identical behaviro shift cfile=${hfile/%h/$t} # TODO: Hopefully this line works. It was the only difference in this case. # compose_includes $cfile $@ # append includes if needed compose_body $hfile # There is a special procedure for creating .h modules compose_header $cfile # create the complementary module c/cpp file if [ $# -gt 0 ]; then compose_includes $cfile $@ # append includes if needed fi include_header $cfile $hfile # TODO: What if instead of compose_inclues, extra includes are added to include_header? ;; *) # For .h files, we just make a .h file. If compose_body is necessary, it will be included later. if [ $# -gt 0 ]; then compose_includes $cfile $@ # append any include files fi ;; # else Do nothing esac # case $1 fi # if $1 ;; *) egress "$0 ERROR: $1 cannot generate a file" ;; esac } # Func: compose # Info: Create a new file # TODO: Includes # STATUS: Work in progress? function compose(){ if [ $# -lt 1 ]; then egress "$0 ERROR! Compose requires at least one file."; fi # Directory generation if [[ ${1%/*} != ${1} ]]; then # only use trailblazer if a directory does not exist trailblazer $(pwd)/${1%/*} # If a new directory is included in the file string, create it. $(pwd)/ added to elimiate endless loop fi # File generation # TODO: Include #includes with an -Ifile.h argument if [ ! -e $1 ]; then # If file does not exist generator $@ # $1 $2 # is this proper syntax? else egress "$0 ERROR: $1 already exists." fi } # Func: compile # Info: execute commands for the compilation step # Note: If you notice in the case block, file extensions are stripped out after -o # TODO: Hopefully error information will output # TODO: Compile multiple items at once. # Status: ALPHA. THIS FUNCTION IS READY TO GO! function compile(){ if [ $# -lt 1 ]; then egress "$0 ERROR: Complie requires at least one file."; fi if [ ! -e $1 ]; then egress "$0 ERROR: $1 does not exist. Aborting."; fi case ${1##*.} in c) gcc -c $1 -o ${1/%.c/.o} ;; cpp) g++ -c $1 -o ${1/%.cpp/.o} ;; *) egress "$0 ERROR: $1 is inappropriate for compilation. Only .c or .cpp files are accepted." ;; esac } # Func: archive # Info: A simple archive of a file. # Usage: # If there is one argument, show the contents of the archive. # If there is more than one argument, and one of the files is a .a file and the rest are .o, archive the .o files to the .a file. # If the .a file does not exist and there are more than two arguments, put all the .o files in the new .a file. # All of the .o files must exist however. # STATUS: ALPHA. MAKE SOME CHANGES! function archive(){ if [ $# -lt 1 ]; then # No arguments = Abort egress "$0 ERROR: Archive requires arguments." elif [ $# -eq 1 ]; then # One argument = List the contents of an archive # View the contents of an archive if [ ! -e $1 ]; then egress "$0 ERROR: Archive $1 does not exists."; fi if [[ "${1##*.}" != "a" ]]; then egress "$0 ERROR: Archive cannot view the contents of $1 because it is not a .a file."; fi ar tv $1 | less -eFMXR # List what's in the file (I've added a less command for convienence) else # elif [ $# -ge 2 ]; then # Two or more arguments = add files # ADD FILES # TODO: procedures for removing files from archives. (Later.) # TODO: The plan: # [x] Check the list of arguments for file existance. If any of the files does not exist, throw an error and exit. # [x] The following arguments should only be .o or .a files. If any of the files are not .o or .a, throw an error and exit. # [x] There should be ONLY ONE .a file in the following arguments. # [x] If there is more than one .a, throw an error and exit. # [x] If there is none, throw an error and exit. # [x] Set the .a file as the second argument of an ar command. (The first argument is the options.) # [x] If a .o file already exists in the .a file, it will be REPLACED or UPDATED (man ar for details.) # TODO: What if I want to delete files from an archive? local afile declare -a fl # List of .o files declare -i i=0 # File count/index declare -i af=0 # a file count. If this is greater than 1, throw an error local ops # Archive options while [ $# -gt 0 ]; do # while loop executes so as long as there are arguments left to execute case ${1##*.} in a) # if $t is "a", that is $1 is an .a file if [ $af -ge 1 ]; then egress "$0 ERROR: I can only handle one .a file. $afile has already been designated. Remove $1. Aborting."; fi if [ ! -e $1 ]; then ops="cv" # Create a new archive later else ops="urv" # Update and replace files in an existing archive later. fi afile=$1 # Otherwise, set the archive index value (( af++ )) # increment the archive file count to 1 ;; o) if [ ! -e $1 ]; then egress "$0 ERROR: $1 does not exist. Aborting."; fi fl[$i]=$1; (( i++ )) ;; *) egress "$0 ERROR: $1 cannot be used for archiving. Aborting." ;; esac shift done if [ $af -eq 0 ]; then egress "$0 ERROR: No archive file was indicated in this list. Aborting."; fi # If no .a file was detected ar $ops $afile ${fl[@]} # Execute ar command fi } # Func: link # Info: Link a set of files together. # TODO: Includes # TODO: Libraries # TODO: Archives # TODO: Options for OpenGL # TODO: Can I just compile with ld or would using gcc and g++ still be better? # Status: ALPHA function link(){ if [ $# -lt 2 ]; then egress "$0 ERROR! Not enough items to link together. Aborting."; fi # TODO: Check each extension # -I # -l$library # List of l # -Wl--start-group ${LIST_OF_FILES[@]} -Wl--end-group -o $PRGM declare -a ol # array of modules declare -a al # array of archives #declare -a ipl # array of Include paths declare -a lpl # array of Library paths declare -i oi=0 declare -i ai=0 #declare -i ipi=0 declare -i lpi=0 local program # The name of the program while [ $# -gt 0 ]; do case ${1##*.} in o) ol[$oi]=$1; (( oi++ )) ;; # Modules a) al[$ai]=$1; (( ai++ )) ;; # Archives (For local libraries) *) # Other options pre=${1#-[LloO]} case ${1%${pre}} in # -I) ;; -L) lpl[$lpi]=$1; (( lpi++ )) ;; # -L$1 # Search library path -l) al[$ai]=$1; (( ai++ )) ;; # -l$1 # This is for system libraries -o) cmpl="gcc"; shift; program=$1 ;; # Link with gcc, name the program -O) cmpl="g++"; shift; program=$1 ;; # Link with g++, name the program # -g) ;; # Link OpenGL with gcc # -G) ;; # Link OpenGL with g++ *) egress "$0 ERROR: Invalid compiler tag" ;; esac ;; esac shift done local attr="-Wl--start-group" if [ ${#ol[@]} -gt 0 ]; then attr="$attr ${ol[@]}"; fi if [ ${#al[@]} -gt 0 ]; then attr="$attr ${al[@]}"; fi if [ ${#lpl[@]} -gt 0 ]; then attr="$attr ${lpl[@]}"; fi attr="$attr -Wl--end-group" # gcc -Wl--start-group ${ol[@]} ${al[@]} -Wl--end-group -o $program if [[ "$cmpl" == "gcc" ]]; then gcc $attr -o $program elif [[ "$cmpl" == "g++" ]]; then g++ $attr -o $program else egress "$0 ERROR: define a -o or -O argument to compile with gcc or g++, respectively." fi } # Func: pilelink # Info: Short for "Compile and Link". # This function is designed for compiling and linking a simple C/C++ file. # Status: ALPHA. THIS FUNCTION SHOULD BE READY TO GO! function pilelink(){ if [ $# -lt 1 ]; then egress "$0 ERROR! Quick Complie/Link requires at least one file."; fi if [ ! -e $1 ]; then egress "$0 ERROR! $1 does not exists. Aborting."; fi case ${1##*.} in # File extension c) gcc $1 -o ${2:-${1%.c}} ;; # The last part on both lines should remove the extension cpp) g++ $1 -o ${2:-${1%.cpp}} ;; # On both lines, if $2 is not defined, use ${1%.*} as value *) egress "$0 ERROR: $1 is inappropriate for rapid compilation and linking. Only .c or .cpp files are accepted." ;; esac } # Func: debug # Info: Call the debugger, gdb, to debug the program # Status: ALPHA. # TODO: $1 needs to be a program, check to see if it is executable. function debug(){ if [ $# -lt 1 ]; then egress "$0 ERROR: Debugger requires at least one argument."; fi if [ ! -e $1 ]; then egress "$0 ERROR: $1 does not exists. There is your bug."; fi if [ ! -x $1 ]; then egress "$0 ERROR: $1 is not an executable. Debugger debugs executable programs."; fi prgm=$1 shift if [ $# -lt 1]; then gdb $prgm # Debug without arguments else gdb $prgm $@ # Debug with arguments fi } if [[ ( $# -eq 0 ) ]]; then usage; fi action=$1 shift case $action in -h|--help) usage ;; # Show the help -w|--write|--compose) compose $@ ;; # Create a new file, and more! -c|--compile) compile $@ ;; # Compile a file. -a|--archive) archive $@ ;; # Collect frequent modules into an archive -l|--link) link $@ ;; # Link modules and archives together to form a program -[oO]) pilelink $@ ;; # Simple compile and link -d|--debug) debug $@ ;; # This should call GDB (FUTURE PLANS) *) egress "ERROR: Invaid request." ;; esac