├── .gitignore ├── README.md └── jcombine.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Vim swap files 2 | *.swp 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jcombine 2 | 3 | ## Introduction 4 | A Python script to combine multiple Java source files into a single file. 5 | 6 | ## Usage 7 | `python3 jcombine.py source_dir target_file` 8 | 9 | Use `jcombine.py --help` for more help. 10 | 11 | **Example:** `python3 jcombine.py /home/ramin/project/src /home/ramin/Main.java` 12 | 13 | **Caution:** A source file with the same name as the target file must be present in the source directory (or its subdirectories). 14 | 15 | ## History 16 | This script was written during the Advanced Programming course I took at [Tehran Polytechnic (AUT)][1], 17 | in order for me and my friends to easily upload a single Java file to [Quera][2], where our exercises where hosted. 18 | 19 | ## Contribution 20 | If you find any problems or have any suggestions, feel free to open a pull request or start an issue and get in touch with me. 21 | 22 | ## Gratitude 23 | In case you find this script useful, I'd be glad if you could give it a star. 24 | 25 | [1]: https://aut.ac.ir/en 26 | [2]: https://quera.org/ 27 | -------------------------------------------------------------------------------- /jcombine.py: -------------------------------------------------------------------------------- 1 | """jcombine.py""" 2 | 3 | import os 4 | import sys 5 | 6 | VERSION_NUMBER = "1.0.0" 7 | CREDITS = "Created by Mehrshad Khansarian at Tehran Polytechnic" 8 | START_DATE = "Started Ordibehesht '82" 9 | LAST_UPDATE = "Last updated Ordibehesht '83" 10 | 11 | def has_main_method(file) -> bool: 12 | with open(file, "r", encoding="utf-8") as input_file: 13 | while True: 14 | line = input_file.readline() 15 | 16 | if line.strip().startswith("public static void main"): 17 | return True 18 | 19 | if not line: 20 | break 21 | 22 | return False 23 | 24 | def contains_item_ending_with(txt: str, strings) -> bool: 25 | """Returns true if the list contains and item ending with 'txt'.""" 26 | for string in strings: 27 | if string.endswith(txt): 28 | return True 29 | 30 | return False 31 | 32 | def remove_element_ending_with(txt, my_list) -> bool: 33 | """Returns true if found and removed, otherwise false.""" 34 | for item in my_list: 35 | if item.endswith(txt): 36 | my_list.remove(item) 37 | return True 38 | 39 | return False 40 | 41 | def get_java_files(file_paths): 42 | """Returns all files paths ending in '.java'.""" 43 | final_file_paths = [file_path for file_path in file_paths \ 44 | if file_path.endswith(".java")] 45 | 46 | return final_file_paths 47 | 48 | def get_all_file_paths(directory_path: str): 49 | """Retrieve full file paths to all the files in the current directory """ 50 | """and all subsequent subdirecctories.""" 51 | file_paths = [] 52 | 53 | for dirpath, dirnames, filenames in os.walk(directory_path): 54 | for filename in filenames: 55 | file_paths.append(os.path.join(dirpath, filename)) 56 | 57 | return file_paths 58 | 59 | def main(): 60 | directory_path: str 61 | output_file_path: str 62 | 63 | if len(sys.argv) == 1: 64 | # Custom, pre-defined paths for ease of use. 65 | directory_path = "$HOME/Desktop/programs/uni/2/hw/" 66 | "hw3/HW3/src/main/java/com/example" 67 | output_file_path = "$HOME/Desktop/temp/main/Main.java" 68 | elif len(sys.argv) == 2: 69 | if sys.argv[1] == '-h' or sys.argv[1] == '--help': 70 | print("usage: python3 jcombine.py source_dir target_file") 71 | print(" A source file with the same name as the target file ") 72 | print(" must be present in the source directory (or its subdirectories).") 73 | print() 74 | print(" Use [-v | --version] for version information.") 75 | elif sys.argv[1] == '-v' or sys.argv[1] == '--version': 76 | print(f"v{VERSION_NUMBER}") 77 | print(CREDITS) 78 | print(START_DATE) 79 | print(LAST_UPDATE) 80 | 81 | sys.exit() 82 | elif len(sys.argv) == 3: 83 | directory_path = sys.argv[1] 84 | output_file_path = sys.argv[2] 85 | else: 86 | print("error: invalid usage", file=sys.stderr) 87 | print("usage: python3 jcombine.py source_dir target_file", file=sys.stderr) 88 | print("use `--help' option for more help", file=sys.stderr) 89 | sys.exit() 90 | 91 | # Expand shell variables in each path. 92 | directory_path = os.path.expandvars(directory_path) 93 | output_file_path = os.path.expandvars(output_file_path) 94 | 95 | if not os.path.isdir(directory_path): 96 | print(f"error: {directory_path} does not exist or is not a directory", \ 97 | file=sys.stderr) 98 | sys.exit() 99 | 100 | imports = set() 101 | 102 | file_paths = get_all_file_paths(directory_path) 103 | file_paths = get_java_files(file_paths) 104 | 105 | dirname_of_output_file = os.path.dirname(output_file_path) 106 | if dirname_of_output_file == directory_path: 107 | print("error: target_file shall not be in source_dir", \ 108 | file=sys.stderr) 109 | print(" please provide another location for target_file", \ 110 | file=sys.stderr) 111 | sys.exit() 112 | 113 | main_filename = os.path.basename(output_file_path) 114 | if not contains_item_ending_with(main_filename, file_paths): 115 | print(f"error: no {main_filename} found in {directory_path}", \ 116 | file=sys.stderr) 117 | sys.exit() 118 | 119 | main_file_path_in_directory_path = os.path.join(directory_path, main_filename) 120 | if not has_main_method(main_file_path_in_directory_path): 121 | print(f"error: {main_filename} does not include " 122 | "the main method, i.e., public static void main(...)", file=sys.stderr) 123 | sys.exit() 124 | 125 | main_file_path: str 126 | for file_path in file_paths: 127 | if file_path.endswith(main_filename): 128 | main_file_path = file_path 129 | break 130 | 131 | file_paths.insert(0, file_paths.pop(file_paths.index(main_file_path))) 132 | 133 | for file_path in file_paths: 134 | with open(file_path, "r", encoding="utf-8") as input_file: 135 | while True: 136 | line = input_file.readline() 137 | 138 | if not line or line.startswith("public"): 139 | break 140 | 141 | if line.startswith("import"): 142 | imports.add(line) 143 | 144 | java_imports = [import_statement for import_statement in imports \ 145 | if import_statement.startswith("import java")] 146 | 147 | try: 148 | output_file = open(output_file_path, "w", encoding="utf-8") 149 | except OSError: 150 | print("error: cannot open output file", file=sys.stderr) 151 | sys.exit() 152 | 153 | output_file.write("// Combined into a single file with jcombine.py\n") 154 | 155 | with open(main_file_path, "r", encoding="utf-8") as input_file: 156 | line = input_file.readline() 157 | if line.startswith("package"): 158 | # Whether to include the package name. 159 | # output_file.write(line) 160 | pass 161 | output_file.write("\n") 162 | 163 | for line in java_imports: 164 | output_file.write(line) 165 | 166 | file_paths.remove(main_file_path) 167 | while True: 168 | line = input_file.readline() 169 | 170 | if not line: 171 | break 172 | 173 | if line.startswith("import"): 174 | continue 175 | 176 | output_file.write(line) 177 | 178 | for file_path in file_paths: 179 | with open(file_path, "r", encoding="utf-8") as input_file: 180 | while True: 181 | line = input_file.readline() 182 | 183 | if not line: 184 | output_file.write("\n") 185 | break 186 | 187 | if line.startswith("import") or line.startswith("package"): 188 | continue 189 | 190 | if (line.startswith("public class") 191 | or line.startswith("public abstract class") 192 | or line.startswith("public enum") 193 | or line.startswith("public interface")): 194 | line = line.removeprefix("public ") 195 | output_file.write(line) 196 | 197 | output_file.close() 198 | 199 | if __name__ == '__main__': 200 | main() 201 | --------------------------------------------------------------------------------