├── .gitignore ├── CHANGELOGS.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── USAGE.md ├── pyproject.toml ├── requirements.txt ├── src ├── main.py └── mkparse │ ├── __init__.py │ └── mkparse.py └── tests ├── resources └── Makefiles │ └── test.Makefile └── unittest.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Files/Folders to be ignored by git 2 | 3 | ## Python Folders 4 | 5 | ### Cache 6 | __pycache__/ 7 | 8 | ### Virtual Environments 9 | env/ 10 | 11 | ### Python Packaging (Setuptools) 12 | *.egg-info/ 13 | dist/ 14 | 15 | ## Python Files 16 | -------------------------------------------------------------------------------- /CHANGELOGS.md: -------------------------------------------------------------------------------- 1 | # CHANGELOGS 2 | 3 | ## Table of Contents 4 | + [2024-03-23](#2024-03-23) 5 | + [2024-03-24](#2024-03-24) 6 | + [2024-03-25](#2024-03-25) 7 | + [2024-03-27](#2024-03-27) 8 | + [2024-03-28](#2024-03-28) 9 | + [2024-03-29](#2024-03-29) 10 | + [2024-03-30](#2024-03-30) 11 | + [2024-04-04](#2024-04-04) 12 | 13 | ## Entry Logs 14 | ### 2024-03-23 15 | #### 1552H 16 | + Initial Commit 17 | + Version: v0.1.0 18 | 19 | - New 20 | + Added new file '.gitignore' 21 | + Added new document 'CHANGELOGS.md' 22 | + Added new document 'README.md' 23 | + Added new document 'USAGE.md' 24 | + Added new document 'requirements.txt' : Python packages dependencies 25 | - Added new directory 'src/' : Contains the project source codes 26 | + Added new python source file 'main.py' : Simple proof-of-concept implementation and unit test source file 27 | - Added new directory 'mkparse' : The Makefile-to-Python Parser package 28 | + Added new source file 'mkparse.py' : The primary Makefile-to-Python Parser Module 29 | 30 | #### 1628H 31 | + Version: v0.2.0 32 | 33 | - New 34 | + Added python packaging script 'setup.py' for setuptools' 35 | 36 | - Updates 37 | - Updated document '.gitignore' 38 | + Added files 39 | - Updated source file 'main.py' in 'src/' 40 | + Fixed formatting 41 | 42 | ### 2024-03-24 43 | #### 2204H 44 | - Updates 45 | - Updated source file 'mkparser.py' in 'src/mkparser' 46 | + Added Makefile parser implementation (to be tested) 47 | 48 | #### 2208H 49 | - Updates 50 | - Updated document 'README.md' 51 | + Fixed pip install URL 52 | 53 | #### 2344H 54 | - New 55 | - Added new directory 'tests/' for writing unit tests and other tests 56 | + Added new unit test source 'unittest.py' for holding the main unit tests 57 | - Added new directory 'resources/' for holding test resource files 58 | - Added new directory 'Makefiles' for holding Makefile resources 59 | + Added new test Makefile 'test.Makefile' 60 | 61 | - Updates 62 | - Updated document 'README.md' 63 | + Added new instruction for installing package in editable local development mode 64 | - Updated document 'requirements.txt' 65 | + Added the repository's github link as an installable pip package 66 | - Updated source file 'main.py' in 'src/' 67 | - Updated source file 'mkparse.py' in 'src/mkparse' 68 | + Swapped parameter/argument signatures 69 | + The variable dictionary now returns the operator (string), and the values (list) mapped to the variable name 70 | - Fixing implementation of Makefile Parser in 'parse_makefiles();' 71 | - Target is still bugged 72 | + Having issues detecting if dependencies is found 73 | 74 | ### 2024-03-25 75 | #### 1202H 76 | - Updates 77 | - Updated unit test file 'unittest.py' in 'tests/' 78 | + Modified to match test requirements 79 | - Updated source file 'mkparse.py' in 'src/mkparse' 80 | + Fixed Makefile to Python parser logic - To be tested 81 | + Wrote documentation comment headers in function 82 | - Added new function 'export_makefile' to export the target and variable dictionaries into a proper Makefile structure 83 | + To be tested with print statement first 84 | 85 | #### 1335H 86 | - Updates 87 | - Updated unit test file 'unittest.py' in 'tests/' 88 | + Modified to match test requirements 89 | - Updated source file 'mkparse.py' in 'src/mkparse' 90 | - Fixed function 'export_Makefile' 91 | + Able to export to a Makefile 92 | - TODO 93 | + To implement reading of comments outside of variables and targets 94 | 95 | #### 1430H 96 | + Version: v0.3.0 97 | 98 | - Version Changes 99 | + Added working Makefile-to-Python import/parser support 100 | + Added working Python-to-Makefile export function 101 | 102 | - TODO Ideas 103 | + Add comment import such that all comments on the global scope (not tied to any targets) will be retrievable 104 | 105 | - Updates 106 | - Updated source file 'mkparse.py' in 'src/mkparse' 107 | - Function 'makefile_parse()' 108 | + Added logical check for comments ('#') and to map that line number to the comment for future use 109 | + Added 'comments' to the return list 110 | + Added 'comments' to the documentation multiline docstring 111 | - Updated document 'USAGE.md' 112 | + Fixed usage example of 'parse_makefile()': Swapped the argument signatures 113 | + Updated return list of 'parse_makefile()' 114 | + Added documentation for function 'export_makefile()' 115 | + Updated general usage examples 116 | 117 | #### 1552H 118 | - New 119 | + Added new '__init__.py' package/module constructor/initializor script in 'src/mkparse' 120 | 121 | - Updates 122 | - Updated Python packaging script 'setup.py' for setuptools 123 | + Replaced 'find_packages()' with a statically-defined package structure 124 | 125 | #### 1555H 126 | + Version: 0.3.1 127 | 128 | - Version Changes 129 | + Fixed 'setup.py' python packaging via setuptools 130 | 131 | #### 1956H 132 | + Version: 0.3.2 133 | 134 | - Version Changes 135 | + Fixed 'setup.py' python packaging via setuptools 136 | 137 | #### 2010H 138 | - Updates 139 | - Updated Python packaging script 'setup.py' for setuptools 140 | + Testing fix for python packaging via setuptools 141 | 142 | #### 2137H 143 | + Version: 0.3.3 144 | 145 | - Version Changes 146 | + Removed unnecessary prints from 'parse_makefile()' 147 | + Error message return in 'export_makefile()' 148 | + Explicit return type definition in 'export_makefile()' 149 | 150 | - Updates 151 | - Updated document 'README.md' 152 | + Updated version number to 0.3.3 153 | - Updated Python packaging script 'setup.py' for setuptools 154 | + Updated version number to 0.3.3 155 | - Updated source file 'mkparse.py' in 'src/mkparse' 156 | - Function 'parse_makefile()' 157 | + Removed 'Sanity Check' print 158 | - Function 'export_makefile()' 159 | + Added explicit return type 160 | + Added error message substitution and return 161 | - Updated unit test file 'unittest.py' in 'tests/' 162 | + Added error message output in the test for export_makefile 163 | 164 | #### 2217H 165 | + Version: v0.4.0 166 | 167 | - Version Changes 168 | - mkparse 169 | + Added new function 'format_makefile_Contents(...)': Function to format the imported targets and variables into printable string then appended into lists of the respective types 170 | 171 | - Updates 172 | - Updated document 'README.md' 173 | + Updated version number to 0.4.0 174 | - Updated Python packaging script 'setup.py' for setuptools 175 | + Updated version number to 0.4.0 176 | - Updated source file 'mkparse.py' in 'src/mkparse' 177 | + Added new function 'format_makefile_Contents': Function to format the imported targets and variables into printable string then appended into lists of the respective types 178 | - Updated unit test file 'unittest.py' in 'tests/' 179 | + Performed some cleanup 180 | + Added unit test for 'format_makefile_Contents' 181 | + Formatted unit tests 182 | - Updated document 'USAGE.md' 183 | + Added documentation for function 'format_makefile_Contents()' 184 | + Added usage examples for the Makefile imported data-into-string formatting 185 | 186 | ### 2024-03-27 187 | + Version: v0.4.1 188 | 189 | - Version Changes 190 | - mkparse 191 | + Added new function 'trim_contents(...)': Function to Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 192 | 193 | - Updates 194 | - Updated document 'README.md' 195 | + Updated version number to 0.4.1 196 | - Updated Python packaging script 'setup.py' for setuptools 197 | + Updated version number to 0.4.1 198 | - Updated source file 'mkparse.py' in 'src/mkparse' 199 | + Added new function 'trim_contents(...)': Function to Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 200 | - Updated unit test file 'unittest.py' in 'tests/' 201 | + Performed some cleanup 202 | + Added unit test for 'trim_contents' 203 | + Formatted unit tests 204 | - Updated document 'USAGE.md' 205 | + Added documentation for function 'trim_contents' 206 | + Fixed documentation for function 'format_makefile_Contents' 207 | + Added usage examples for function 'trim_contents' 208 | 209 | ### 2024-03-28 210 | #### 1123H 211 | + Version: v0.4.2 212 | 213 | - Version Changes 214 | - mkparse 215 | - Function 'format_makefile_Contents()' 216 | + Updated to set default value 'None' to parameter/arguments so that developer/user can choose to format either the targets, variables or both 217 | 218 | - Updates 219 | - Updated document 'README.md' 220 | + Updated version number to 0.4.2 221 | - Updated Python packaging script 'setup.py' for setuptools 222 | + Updated version number to 0.4.2 223 | - Updated source file 'mkparse.py' in 'src/mkparse' 224 | - Function 'format_makefile_Contents()' 225 | + Updated to set default value 'None' to parameter/arguments so that developer/user can choose to format either the targets, variables or both 226 | - Updated unit test source file 'unittest.py' in 'tests/' 227 | + Added unit test for testing the function 'format_makefile_Contents()': Passing only the Makefile 'targets' dictionary 228 | + Added unit test for testing the function 'format_makefile_Contents()': Passing only the Makefile 'variables' dictionary 229 | 230 | #### 1342H 231 | - Updates 232 | - Updated source file 'mkparse.py' in 'src/mkparse' 233 | - Function 'trim_contents()' 234 | + Attempting to modify function to return results depending on input for dynamic usability 235 | 236 | #### 1353H 237 | - Updates 238 | - Updated unit test source file 'unittest.py' in 'tests/' 239 | + Added unit test for testing the function 'trim_contents()': Attempting to modify function to return results depending on input for dynamic usability 240 | 241 | #### 1401H 242 | - Updates 243 | - Updated document 'USAGE.md' 244 | + Updated document with new usage examples and updated parameter explanations 245 | 246 | #### 1411H 247 | - Updates 248 | - Updated document 'USAGE.md' 249 | - Updated '.trim_contents' 250 | + Added dynamic return values 251 | 252 | #### 1457H 253 | + Version: v0.4.3 254 | 255 | - Version Changes 256 | - mkparse 257 | - Function '.format_makefile_Contents()' 258 | + 'targets' and 'variables' dictionary are now optional inputs 259 | - Function '.trim_contents()' 260 | + 'targets' and 'variables' dictionary are now optional inputs 261 | + Function returns results depending on input for dynamic usability 262 | 263 | - Updates 264 | - Updated document 'README.md' 265 | + Updated version number to 0.4.3 266 | - Updated Python packaging script 'setup.py' for setuptools 267 | + Updated version number to 0.4.3 268 | 269 | #### 1942H 270 | - Updates 271 | - Updated source file 'mkparse.py' in 'src/mkparse' 272 | - Fixing import bug whereby 'parse_makefile()' is unable to store variable lines without spaces 273 | - i.e. 274 | ```makefile 275 | variable=value 276 | ``` 277 | 278 | #### 2043H 279 | - Updates 280 | - Updated source file 'mkparse.py' in 'src/mkparse' 281 | - Fixing import bug whereby 'parse_makefile()' is unable to store variable lines without spaces 282 | + Added additional layer of validation if a variable has no spaces by the delimiter (i.e. 'variable_name=value' instead of 'variable_name = value') 283 | - Added checks for the keywords ':=', '?=' and ':' 284 | - Check the variable name (index 1) for the occurence of any of the above keywords 285 | + Resize and replace lists if delimiter is obtained 286 | 287 | #### 2159H 288 | - Updates 289 | - Updated source file 'mkparse.py' in 'src/mkparse' 290 | - Function 'parse_makefile()' 291 | + Refactored delimiter check 292 | 293 | #### 2205H 294 | - Updates 295 | - Updated source file 'mkparse.py' in 'src/mkparse' 296 | - Function 'parse_makefile()' 297 | - Fixing import bug whereby 'parse_makefile()' is unable to store variable lines without spaces 298 | + Modified positioning and performed cleanup 299 | + Set operator directly in the operator index checker to obtain the specific various separator ('=', ':=', '?=') 300 | 301 | ### 2024-03-29 302 | #### 1007H 303 | + Version: v0.4.4 304 | 305 | - Version Changes 306 | - mkparse 307 | - Function 'parse_makefile()' 308 | - Fixed import bug whereby 'parse_makefile()' is unable to store variable lines without spaces 309 | + Added additional layer of validation if a variable has no spaces by the delimiter (i.e. 'variable_name=value' instead of 'variable_name = value') 310 | - Added checks for the keywords ':=', '?=' and ':' 311 | - Check the variable name (index 1) for the occurence of any of the above keywords 312 | + Resize and replace lists if delimiter is obtained 313 | 314 | - Updates 315 | - Updated document 'README.md' 316 | + Updated version number to 0.4.4. 317 | - Updated Python packaging script 'setup.py' for setuptools 318 | + Updated version number to 0.4.4 319 | - Updated source file 'mkparse.py' in 'src/mkparse' 320 | - Function 'parse_makefile()' 321 | - Fixed import bug whereby 'parse_makefile()' is unable to store variable lines without spaces 322 | + Added additional layer of validation if a variable has no spaces by the delimiter (i.e. 'variable_name=value' instead of 'variable_name = value') 323 | - Added checks for the keywords ':=', '?=' and ':' 324 | - Check the variable name (index 1) for the occurence of any of the above keywords 325 | + Resize and replace lists if delimiter is obtained 326 | 327 | #### 1116H 328 | - New 329 | + Added new document 'CONTRIBUTING.md' : Documenting contribution steps 330 | 331 | - Updates 332 | - Updated source file 'mkparse.py' in 'src/mkparse' 333 | - Function 'parse_makefile()' 334 | + Fixed the no lines issues 335 | + Refactored variable positioning 336 | 337 | #### 1118H 338 | + Version: v0.4.5 339 | 340 | - Version Changes 341 | - mkparse 342 | - Function 'parse_makefile()' 343 | - Fixed import bug whereby 'parse_makefile()' is unable to store variable lines without spaces 344 | + Added additional layer of validation if a variable has no spaces by the delimiter (i.e. 'variable_name=value' instead of 'variable_name = value') 345 | - Added checks for the keywords ':=', '?=' and ':' 346 | - Check the variable name (index 1) for the occurence of any of the above keywords 347 | + Resize and replace lists if delimiter is obtained 348 | 349 | - New 350 | + Added new document 'CONTRIBUTING.md' : Documenting contribution steps 351 | 352 | - Updates 353 | - Updated document 'README.md' 354 | + Updated version number to 0.4.5 355 | - Updated Python packaging script 'setup.py' for setuptools 356 | + Updated version number to 0.4.5 357 | - Updated source file 'mkparse.py' in 'src/mkparse' 358 | + Performed cleanup 359 | - Function 'parse_makefile()' 360 | + Fixed the no lines issues 361 | + Refactored variable positioning 362 | 363 | #### 1136H 364 | + Version: v0.4.6 365 | 366 | - Version Changes 367 | - mkparse 368 | - Function 'parse_makefile()' 369 | - Added '1' to the 2nd parameter of '.split(delimiter)' to specify a maximum number of searches of occurences of the specified delimiter 370 | + Basically, the goal is to search for only the first occurence of '=', '?=' or ':=', and if found - thats the delimiter 371 | 372 | - Updates 373 | - Updated document 'README.md' 374 | + Updated version number to 0.4.6 375 | - Updated Python packaging script 'setup.py' for setuptools 376 | + Updated version number to 0.4.6 377 | - Updated document 'CONTRIBUTING.md' 378 | + Added new header block 'Debugging' for Debugging snippets 379 | - Updated source file 'mkparse.py' in 'src/mkparse' 380 | + Performed cleanup 381 | - Function 'parse_makefile()' 382 | - Added '1' to the 2nd parameter of '.split(delimiter)' to specify a maximum number of searches of occurences of the specified delimiter 383 | + Basically, the goal is to search for only the first occurence of '=', '?=' or ':=', and if found - thats the delimiter 384 | 385 | ### 2024-03-30 386 | #### 0013H 387 | - Updates 388 | - Updated document 'USAGE.md' 389 | - Added documentation for new function 'parse_makefile_string(self, makefile_string="")' 390 | + Added usage examples 391 | - Updated source file 'mkparse.py' in 'src/mkparse' 392 | + Added new function `parse_makefile_string(self, makefile_string="")`: To parse Makefile strings into the system without a file 393 | - Updated unit test file 'unittest.py' in 'tests/' 394 | + Added unit test for parsing a template Makefile string into system, exporting it for comparison, then pretty printing the containers into standard output 395 | 396 | #### 0026H 397 | - Updates 398 | - Updated document 'USAGE.md' 399 | + Added documentation for new function 'ast_parse()' 400 | - Updated source file 'mkparse.py' in 'src/mkparse' 401 | + Added new function `ast_parse(self, makefile_string_contents=None)`: The Makefile parser core unit; the parsing will go through this 402 | + Migrated the parsing functionality of 'parse_makefile_string()' to 'ast_parse()' 403 | 404 | #### 0034H 405 | + Version: v0.5.0 406 | 407 | - Version Changes 408 | - mkparse 409 | - Added new function `parse_makefile_string(self, makefile_string="")`: To parse Makefile strings into the system without a file 410 | + Functionality to import not just from a file, but from a string 411 | - Added new function `ast_parse(self, makefile_string_contents=None)`: The Makefile parser core unit; the parsing will go through this 412 | + Separate core parser logic containing the 'AST' of a Makefile allows standalone implementation of the Makefile logic in other functions 413 | + Migrated the parsing functionality of 'parse_makefile_string()' to 'ast_parse()' 414 | + Renamed class 'MakefileParser' => 'Parser' 415 | 416 | - Updates 417 | - Updated document 'README.md' 418 | + Updated version number to 0.5.0 419 | - Updated Python packaging script 'setup.py' for setuptools 420 | + Updated version number to 0.5.0 421 | - Updated document 'USAGE.md' 422 | - Added documentation for new function 'parse_makefile_string(self, makefile_string="")' 423 | + Added usage examples 424 | + Added documentation for new function 'ast_parse()' 425 | + Updated class entry 'MakefileParser(...)' => 'Parser(...)' 426 | - Updated source file 'mkparse.py' in 'src/mkparse' 427 | + Added new function 'parse_makefile_string(self, makefile_string="")': To parse Makefile strings into the system without a file 428 | + Added new function 'ast_parse(self, makefile_string_contents=None)': The Makefile parser core unit; the parsing will go through this 429 | + Migrated the parsing functionality of 'parse_makefile_string()' to 'ast_parse()' 430 | + Renamed class 'MakefileParser' => 'Parser' 431 | - Updated unit test file 'unittest.py' in 'tests/' 432 | + Added unit test for parsing a template Makefile string into system, exporting it for comparison, then pretty printing the containers into standard output 433 | + Updated import from 'MakefileParser' => 'Parser' 434 | + Added function 'init()' to initialize the class variables 435 | + Removed all initialization of class variable 'Parser' except in the 'init()' function 436 | 437 | ### 2024-04-04 438 | #### 2102H 439 | - New 440 | + Added new python packaging toml configuration file 'pyproject.toml' for replacing setup.py 441 | 442 | 443 | #### 2108H 444 | + Version: v0.6.0 445 | 446 | - Version Changes 447 | - Repository changes of note 448 | + Replaced 'setup.py' with 'pyproject.toml' which not only allows for use of setuptools, but compatibility with other build systems 449 | - Bug Fixes 450 | + Fixed bug where library/modules can be imported in Linux after installation, but has issues being imported on Windows 451 | 452 | - New 453 | + Added new python packaging toml configuration file 'pyproject.toml' for replacing setup.py 454 | 455 | - Updates 456 | - Updated document 'README.md' 457 | + Updated package version to '0.6.0' 458 | - Replaced 'setup.py' with 'pyproject.toml' which not only allows for use of setuptools, but compatibility with other build systems 459 | + Updated package version to '0.6.0' 460 | 461 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing Steps 2 | ================== 3 | 4 | ## Table of Contents 5 | + [Contribution Rules](#contribution-rules) 6 | + [Contribution Operational Workflow](#contribution-operational-workflow) 7 | + [Setup](#setup) 8 | + [Development](#development) 9 | + [Resources](#resources) 10 | + [References](#references) 11 | + [Remarks](#remarks) 12 | 13 | ## Contribution Rules 14 | + Please do not push directly to the main branch, always create a Pull Request to merge your fork/branch 15 | - Please write a quality commit message, providing a detailed write-up of your changes 16 | - At minimum, following the format below 17 | ``` 18 | Title (Purpose/Reason for change): 19 | Author Name: 20 | 21 | Files Changes: 22 | - file-name-1 23 | - file-name-2 24 | - file-name-N 25 | 26 | Change Summary: 27 | - Description here 28 | ``` 29 | 30 | ## Contribution Operational Workflow 31 | + Clone repository and enter local repository directory 32 | + Create a new branch/fork 33 | 34 | ## Setup 35 | 36 | *Dependencies* 37 | -------------- 38 | + python 39 | + python-pip 40 | + python-venv 41 | 42 | *Pre-Requisites* 43 | ---------------- 44 | - Create Python Virtual Environments 45 | - Generate Virtual Environments 46 | ```bash 47 | python3 -m venv [virtual-environment-name] 48 | ``` 49 | 50 | - Chroot into Virtual Environment 51 | - Linux 52 | ```bash 53 | . [virtual-environment-name]/bin/activate 54 | ``` 55 | - Windows 56 | ```bash 57 | .\[virtual-environment-name]\Scripts\activate 58 | ``` 59 | 60 | - Install Python Packages/Dependencies 61 | ```bash 62 | pip install -Ur requirements.txt 63 | ``` 64 | 65 | - Verify packages 66 | ```bash 67 | pip freeze list 68 | ``` 69 | 70 | - Initial Setup 71 | - GitHub (Git Remote Repository Server) 72 | + Create a fork 73 | 74 | - Clone the repository 75 | - Notes 76 | + Change 'Thanatisia' to your account if you created a fork 77 | ```bash 78 | git clone https://github.com/Thanatisia/makefile-parser-python 79 | ``` 80 | 81 | - Change directory into local repository 82 | ```bash 83 | cd makefile-parser-python 84 | ``` 85 | 86 | - Set git configurations 87 | - Username 88 | ```bash 89 | git config user.name [username] 90 | ``` 91 | - Email 92 | ```bash 93 | git config user.email [email] 94 | ``` 95 | 96 | 97 | ## Development 98 | 99 | *Git Workflow* 100 | -------------- 101 | 102 | > Please perform the following for every changes made 103 | 104 | - Create a new git branch 105 | ```bash 106 | git checkout -b [new-branch-name] 107 | ``` 108 | 109 | - Pull latest changes from the main, development branch(es) or your target branch 110 | ```bash 111 | git pull origin [main|development|custom] 112 | ``` 113 | 114 | + Make changes 115 | 116 | - Check git status 117 | ```bash 118 | git status 119 | ``` 120 | 121 | - Check git differences 122 | ```bash 123 | git diff 124 | ``` 125 | 126 | - Add modified files to your git history and prepare for commit 127 | ```bash 128 | git add [files|*|.] 129 | ``` 130 | 131 | - Commit all changes made and added 132 | - Notes 133 | + Please read [Contribution Rules](#contribution-rules) regarding the quality of commit messages 134 | ```bash 135 | git commit -m "[commit-messages]" 136 | ``` 137 | 138 | - Push changes made on local repository to your fork/branch in the remote repository server 139 | ```bash 140 | git push -u origin [branch-name] 141 | ``` 142 | 143 | - When you are ready to create a Pull Request 144 | - GitHub 145 | + Go to your branch in your fork 146 | - Create a Pull Request to merge upstream into the development branch of the main repository 147 | - Please specify the following 148 | + Header/Title Subject: `[author-name]: [Purpose/Reason for change]` 149 | - Body Content 150 | ``` 151 | Date/Time Changed: 152 | Author Name: 153 | 154 | Files Changes: 155 | - file-name-1 156 | - file-name-2 157 | - file-name-N 158 | 159 | Change Summary: 160 | - Description here 161 | ``` 162 | + Create Pull Request 163 | 164 | *Testing Changes* 165 | --------------------- 166 | - Uninstall package 167 | ```bash 168 | pip uninstall mkparse 169 | ``` 170 | 171 | - Install the latest changes in development mode 172 | ```bash 173 | pip install . 174 | ``` 175 | 176 | - Uninstall and install package in one line 177 | ```bash 178 | pip uninstall mkparse; pip install . 179 | ``` 180 | 181 | *Debugging* 182 | ----------- 183 | - `parse_makefile()` 184 | - Debugging the splitting of 'variables' into parts 185 | ```python 186 | print("Parts: {}".format(parts)) 187 | print("\tVariable Name: {}".format(variable_name)) 188 | print("\tOperator: {}".format(operator)) 189 | print("\tVariable Values: {}".format(variable_value)) 190 | ``` 191 | 192 | ## Resources 193 | 194 | ## References 195 | 196 | ## Remarks 197 | 198 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Zachary Lim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Makefile Parser in Python 2 | ========================= 3 | 4 | ## Information 5 | 6 | ### Description 7 | + A simple Makefile Parser written in Python that is designed to simplify the process of importing Makefile contents into python as dictionary (key-value mappings (i.e. hashmap/associative arrays)) objects 8 | + Standalone CLI add-on support may be considered, but currently, the focus is on a working Makefile-to-Python Parser 9 | + Currently still a WIP 10 | 11 | ### Project 12 | + Package Name: mkparser-python 13 | + Current Version: v0.6.0 14 | 15 | ## Setup 16 | 17 | *Dependencies* 18 | -------------- 19 | + python3 20 | + python-pip 21 | + python3-venv 22 | 23 | *Pre-Requisites* 24 | ---------------- 25 | - Create Python Virtual Environments 26 | - Generate Virtual Environments 27 | ```bash 28 | python3 -m venv [virtual-environment-name] 29 | ``` 30 | 31 | - Chroot into Virtual Environment 32 | - Linux 33 | ```bash 34 | . [virtual-environment-name]/bin/activate 35 | ``` 36 | - Windows 37 | ```bash 38 | .\[virtual-environment-name]\Scripts\activate 39 | ``` 40 | 41 | - Install Python Packages/Dependencies 42 | ```bash 43 | pip install -Ur requirements.txt 44 | ``` 45 | 46 | - Verify packages 47 | ```bash 48 | pip freeze list 49 | ``` 50 | 51 | *Installing* 52 | ------------ 53 | - Install locally in development mode 54 | ```bash 55 | pip install . 56 | ``` 57 | 58 | - Install locally in editable development mode 59 | ```bash 60 | pip install -e . 61 | ``` 62 | 63 | - Install Python package using GitHub repository via setuptools 64 | ```bash 65 | pip install git+https://github.com/Thanatisia/makefile-parser-python 66 | ``` 67 | 68 | ## Wiki 69 | 70 | ## Resources 71 | 72 | ## References 73 | 74 | ## Remarks 75 | 76 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # Usage and Customization 2 | 3 | ``` 4 | Information regarding the various ways to use this Makefile parser 5 | ``` 6 | 7 | ## Documentations 8 | 9 | ### Package 10 | - mkparse 11 | 12 | ### Modules 13 | - mkparse 14 | 15 | ### Classes 16 | - `Parser(makefile_name="Makefile", makefile_path="."` : Primary makefile parser class; previously named 'MakefileParser()' 17 | - Class Constructor Parameters 18 | - makefile_name : Specify the file name of the target Makefile 19 | + Type: String 20 | + Default: "Makefile" 21 | - makefile_path : Specify the file path of the target Makefile 22 | + Type: String 23 | + Default: "." (Current Working Directory) 24 | 25 | ### Functions 26 | - MakefileParser 27 | - `.ast_parse(makefile_string_contents=None)`: Makefile parser core unit 28 | - Parameter/Argument Signatures 29 | - makefile_string_contents : Specify the Makefile content body (after splitting by newline) you wish to import and parse into the memory buffer 30 | + Type: String|List 31 | + Default: "" 32 | 33 | - Return 34 | + Type: List 35 | - Values 36 | - targets: Contains the targets/instructions/rules and the attached dependencies and statements 37 | + Type: Dictionary 38 | - Format 39 | { 40 | "target-name" : { 41 | "dependencies" : [your, dependencies, here], 42 | "statements" : [your, statements, here] 43 | } 44 | } 45 | - Key-Value Explanation 46 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 47 | - Key-Value Mappings 48 | - dependencies : Specify a list of all dependencies 49 | - Notes: 50 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 51 | - i.e. 52 | [target-name]: [dependencies ...] 53 | - statements : Specify a list of all rows of statements to write under the target 54 | - variables : Contains the variables and the attached operator (delimiter) and value 55 | + Type: Dictionary 56 | - Format 57 | { 58 | "variable-name" : { 59 | "operator" : "operator (i.e. =|?=|:=)", 60 | "value" : [your, values, here] 61 | } 62 | } 63 | - Key-Value Explanation 64 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 65 | - Key-Value Mappings 66 | - operator : Specify the operator to map the variable to its value string/array/list 67 | + Type: String 68 | - Operator Keyword Types 69 | + '=' 70 | + '?=' 71 | + ':=' 72 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 73 | + Type: List 74 | - comments : Pass the updated global comments list you wish to export (NOTE: Currently unused; for future development plans) 75 | + Type: Dictionary 76 | - Format 77 | { 78 | "line-number" : comment-from-that-line 79 | } 80 | - Key-Value Explanation 81 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 82 | - Key-Value Mappings 83 | - line-number: The line number; this is mapped to the comment stored at that line 84 | - `.parse_makefile(makefile_name="Makefile", makefile_path=".")` : Parse the specified Makefile into python dictionary data objects 85 | - Parameter/Argument Signatures 86 | - makefile_name : Specify the file name of the target Makefile 87 | + Type: String 88 | + Default: "Makefile" 89 | - makefile_path : Specify the file path of the target Makefile 90 | + Type: String 91 | + Default: "." (Current Working Directory) 92 | - Return 93 | + Type: List 94 | - Values 95 | + targets : Makefile rules/targets + dependencies 96 | + variables : Makefile variables/build arguments 97 | + comments : Makefile comments from the global scope (not tied to any targets); Currently unused 98 | - `.parse_makefile_string(makefile_string="")`: Parse a Makefile syntax string into Python dictionary (Key-Value/HashMap) object 99 | - Parameter/Argument Signatures 100 | - makefile_string : Specify the Makefile content body you wish to import and parse into the memory buffer 101 | + Type: String 102 | + Default: "" 103 | - Return 104 | + Type: List 105 | - Values 106 | + targets : Makefile rules/targets + dependencies 107 | + variables : Makefile variables/build arguments 108 | + comments : Makefile comments from the global scope (not tied to any targets); Currently unused 109 | - `.export_Makefile(targets:dict, variables:dict, makefile_name="Makefile", makefile_path=".")` : Export the targets and variables list into an output Makefile 110 | - Parameter/Argument Signatures 111 | - targets: Pass the new targets list you wish to export 112 | + Type: Dictionary 113 | - Format 114 | ```python 115 | { 116 | "target-name" : { 117 | "dependencies" : [your, dependencies, here], 118 | "statements" : [your, statements, here] 119 | } 120 | } 121 | ``` 122 | - Key-Value Explanation 123 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 124 | - Key-Value Mappings 125 | - dependencies : Specify a list of all dependencies 126 | - Notes: 127 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 128 | - i.e. 129 | ```make 130 | [target-name]: [dependencies ...] 131 | ``` 132 | - statements : Specify a list of all rows of statements to write under the target 133 | - Examples 134 | ```make 135 | [target-name]: 136 | # Statements... 137 | ``` 138 | - variables : Pass the new variables list you wish to export 139 | + Type: Dictionary 140 | - Format 141 | ```python 142 | { 143 | "variable-name" : { 144 | "operator" : "operator (i.e. =|?=|:=)", 145 | "value" : [your, values, here] 146 | } 147 | } 148 | ``` 149 | - Key-Value Explanation 150 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 151 | - Key-Value Mappings 152 | - operator : Specify the operator to map the variable to its value string/array/list 153 | + Type: String 154 | - Operator Keyword Types 155 | + '=' 156 | + '?=' 157 | + ':=' 158 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 159 | + Type: List 160 | - makefile_name : Specify the name of the output Makefile to export to 161 | + Type: String 162 | + Default Value: "Makefile" 163 | - makefile_path : Specify the path containing the output Makefile to export to 164 | + Type: String 165 | + Default Value: "." (Current Working Directory) 166 | 167 | - Output 168 | + Type: void/null/None 169 | - Write the provided Makefile specifications to an output Makefile of the following attributes 170 | + File Name: [makefile_path]/[makefile_name] 171 | + File Type: Makefile 172 | - Format: 173 | ``` 174 | [variable-name] = variable values 175 | 176 | [target-name] : your dependencies here 177 | # Instructions/statements 178 | ``` 179 | 180 | - `.format_makefile_Contents(targets=None, variables=None)`: Format provided makefile targets and variables into content strings 181 | - Parameter/Argument Signature 182 | - targets: Specify the Makefile targets to format; Optional 183 | + Type: Dictionary 184 | + Default: None 185 | - Format 186 | { 187 | "target-name" : { 188 | "dependencies" : [your, dependencies, here], 189 | "statements" : [your, statements, here] 190 | } 191 | } 192 | - Key-Value Explanation 193 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 194 | - Key-Value Mappings 195 | - dependencies : Specify a list of all dependencies 196 | - Notes: 197 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 198 | - i.e. 199 | [target-name]: [dependencies ...] 200 | - statements : Specify a list of all rows of statements to write under the target 201 | - variables: Specify the Makefile variables to format; Optional 202 | + Type: Dictionary 203 | + Default: None 204 | - Format 205 | { 206 | "variable-name" : { 207 | "operator" : "operator (i.e. =|?=|:=)", 208 | "value" : [your, values, here] 209 | } 210 | } 211 | - Key-Value Explanation 212 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 213 | - Key-Value Mappings 214 | - operator : Specify the operator to map the variable to its value string/array/list 215 | + Type: String 216 | - Operator Keyword Types 217 | + '=' 218 | + '?=' 219 | + ':=' 220 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 221 | + Type: List 222 | 223 | - Return 224 | - contents: Dictionary (key-value) Mapping of the formatted strings, stored in a list of the targets and variables respectively 225 | + Type: Dictionary 226 | - Key-Value mappings 227 | - targets : List of all targets formatted into a printable string 228 | + Type: List 229 | - variables : List of all variables formatted into a printable string 230 | + Type: List 231 | + Format 232 | ```python 233 | contents = { 234 | "targets" : [], 235 | "variables" : [] 236 | } 237 | ``` 238 | - `.trim_contents(targets=None, variables=None)` : Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 239 | - Parameter/Argument Signature 240 | - targets: Pass the target dictionary mappings you wish to trim/strip; Optional 241 | + Type: Dictionary 242 | + Default: None 243 | - Format 244 | ```python 245 | { 246 | "target-name" : { 247 | "dependencies" : [your, dependencies, here], 248 | "statements" : [your, statements, here] 249 | } 250 | } 251 | ``` 252 | - Key-Value Explanation 253 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 254 | - Key-Value Mappings 255 | - dependencies : Specify a list of all dependencies 256 | - Notes: 257 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 258 | - i.e. 259 | [target-name]: [dependencies ...] 260 | - statements : Specify a list of all rows of statements to write under the target 261 | - variables : Pass the target variables mappings you wish to trim/strip; Optional 262 | + Type: Dictionary 263 | + Default: None 264 | - Format 265 | ```python 266 | { 267 | "variable-name" : { 268 | "operator" : "operator (i.e. =|?=|:=)", 269 | "value" : [your, values, here] 270 | } 271 | } 272 | ``` 273 | - Key-Value Explanation 274 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 275 | - Key-Value Mappings 276 | - operator : Specify the operator to map the variable to its value string/array/list 277 | + Type: String 278 | - Operator Keyword Types 279 | + '=' 280 | + '?=' 281 | + ':=' 282 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 283 | + Type: List 284 | - Return: 285 | - Default: If both 'targets' and 'variables' are specified 286 | + Type: List 287 | - Values 288 | + targets : Trimmed Makefile rules/targets + dependencies 289 | + variables : Trimmed Makefile variables/build arguments 290 | - If only 'variables' is specified 291 | + Type: Dictionary 292 | - Values 293 | + variables : Trimmed Makefile variables/build arguments 294 | - If only 'targets' is specified 295 | + Type: Dictionary 296 | - Values 297 | + targets : Trimmed Makefile rules/targets + dependencies 298 | 299 | ### Data Classes/Types 300 | 301 | ### Attributes/Variables Objects 302 | - MakefileParser 303 | - `.makefile_path` : The specified file path of the target Makefile 304 | + Type: String 305 | - `.makefile_name` : The specified file name of the target Makefile 306 | + Type: String 307 | 308 | ## Usages 309 | 310 | ### As a library 311 | 312 | - Import python package 313 | ```python 314 | from mkparse.mkparse import MakefileParser 315 | ``` 316 | 317 | - Initialize Variables 318 | ```python 319 | # Initialize Variables 320 | makefile_path = "." 321 | makefile_name = "Makefile" 322 | ``` 323 | 324 | - (Optional) Obtain makefile arguments as a CLI argument 325 | ```python 326 | # Initialize Variables 327 | exec = sys.argv[0] 328 | argv = sys.argv[1:] 329 | argc = len(argv) 330 | makefile_path = "." 331 | makefile_name = "Makefile" 332 | 333 | # Get Arguments 334 | if argc >= 2: 335 | makefile_path = argv[0] 336 | makefile_name = argv[1] 337 | ``` 338 | 339 | - Initialize Module Classes 340 | - MakefileParser : The primary makefile file parser 341 | ```python 342 | makefile_parser = MakefileParser(makefile_name, makefile_path) # Initialize Makefile Parser 343 | ``` 344 | 345 | - Import Makefile file contents into python dictionary (key-value mappings; i.e. HashMap/Associative Array) 346 | - Import from a Makefile file 347 | - Notes 348 | - If you do not require any of the return objects 349 | - You can just replace the output object with '_' 350 | - i.e. 351 | ```python 352 | targets, variables, _ = makefile_parser.parse_makefile(makefile_name, makefile_path) 353 | ``` 354 | ```python 355 | # Import Makefile contents into application runtime 356 | targets, variables, comments = makefile_parser.parse_makefile(makefile_name, makefile_path) 357 | ``` 358 | - Import using a Makefile string 359 | ```python 360 | # Import Makefile string into application runtime 361 | makefile_string = """# Makefile 362 | variable = value 363 | 364 | target: dependencies 365 | # statement 366 | """ 367 | targets, variables, comments = makefile_parser.parse_makefile_string(makefile_string) 368 | ``` 369 | 370 | - Process imported Makefile contents 371 | - Trim imported Makefile contents 372 | - Trim both targets and variables 373 | ```python 374 | # Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 375 | targets, variables = makefile_parser.trim_contents(targets, variables) 376 | ``` 377 | - Trim 'targets' dictionary only 378 | ```python 379 | # Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 380 | targets = makefile_parser.trim_contents(targets=targets) 381 | ``` 382 | - Trim 'variables' dictionary only 383 | ```python 384 | # Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 385 | variables = makefile_parser.trim_contents(variables=variables) 386 | ``` 387 | - Format Makefile contents into string 388 | - Format both 'targets' and 'variables' dictionary 389 | ```python 390 | # Format Makefile output into formatted string 391 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(targets, variables) 392 | 393 | # Process imported Makefile contents 394 | formatted_makefile_Targets = formatted_makefile_Contents["targets"] 395 | formatted_makefile_Variables = formatted_makefile_Contents["variables"] 396 | ``` 397 | - Format 'targets' dictionary only 398 | ```python 399 | # Format Makefile output into formatted string 400 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(targets=targets) 401 | 402 | # Process imported Makefile contents 403 | formatted_makefile_Targets = formatted_makefile_Contents["targets"] 404 | ``` 405 | - Format 'variables' dictionary only 406 | ```python 407 | # Format Makefile output into formatted string 408 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(variables=variables) 409 | 410 | # Process imported Makefile contents 411 | formatted_makefile_Targets = formatted_makefile_Contents["variables"] 412 | ``` 413 | 414 | - Use processed data 415 | ```python 416 | print("=========") 417 | print("Variables") 418 | print("=========") 419 | for i in range(len(formatted_makefile_Variables)): 420 | # Get current line 421 | curr_line = formatted_makefile_Variables[i] 422 | # Print 423 | print(curr_line) 424 | 425 | print("") 426 | 427 | print("=======") 428 | print("Targets") 429 | print("=======") 430 | for i in range(len(formatted_makefile_Targets)): 431 | # Get current line 432 | curr_line = formatted_makefile_Targets[i] 433 | # Print 434 | print(curr_line) 435 | 436 | print("") 437 | ``` 438 | 439 | - Output processed data 440 | - Export dictionaries to Makefile 441 | ```python 442 | # Export Makefile dictionaries to Makefile 443 | makefile_parser.export_Makefile(targets, variables, makefile_name, makefile_path) 444 | ``` 445 | 446 | ## Resources 447 | 448 | ## References 449 | 450 | ## Remarks 451 | 452 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name='mkparse' 7 | version='0.6.0' 8 | description="A simple Makefile Parser written in Python that is designed to simplify the process of importing Makefile contents into python as dictionary (key-value mappings (i.e. hashmap/associative arrays)) objects" 9 | authors = [ 10 | { name="Thanatisia", email="55834101+Thanatisia@users.noreply.github.com" }, 11 | ] 12 | readme = "README.md" 13 | requires-python = ">=3.8" 14 | classifiers = [ 15 | # How mature is this project? Common values are 16 | # 3 - Alpha 17 | # 4 - Beta 18 | # 5 - Production/Stable 19 | 'Development Status :: 3 - Alpha', 20 | 21 | # Indicate who your project is intended for 22 | 'Intended Audience :: Developers', 23 | 'Operating System :: OS Independent', 24 | 25 | # Pick your license as you wish 26 | 'License :: OSI Approved :: MIT License', 27 | 28 | # Specify the Python versions you support here. 29 | 'Programming Language :: Python :: 3', 30 | 'Programming Language :: Python :: 3.6', 31 | 'Programming Language :: Python :: 3.7', 32 | 'Programming Language :: Python :: 3.8', 33 | 'Programming Language :: Python :: 3.9', 34 | 'Programming Language :: Python :: 3.10', 35 | 'Programming Language :: Python :: 3.11', 36 | 'Programming Language :: Python :: 3.12', 37 | ] 38 | # install_requires=[ 39 | # # List your dependencies here 40 | # ] 41 | 42 | [project.urls] 43 | Homepage = "https://github.com/Thanatisia/makefile-parser-python" 44 | Repository = "https://github.com/Thanatisia/makefile-parser-python.git" 45 | 46 | [tools.setuptools.packages.find] 47 | where = ["src"] 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Python package requirements/dependencies 2 | 3 | pyright 4 | git+https://github.com/Thanatisia/makefile-parser-python 5 | 6 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main Runner/Launcher 3 | """ 4 | import os 5 | import sys 6 | from mkparse.mkparse import MakefileParser 7 | 8 | def main(): 9 | # Initialize Variables 10 | makefile_path = "." 11 | makefile_name = "Makefile" 12 | makefile_parser = MakefileParser(makefile_name, makefile_path) # Initialize Makefile Parser 13 | 14 | # Import Makefile contents into application runtime 15 | targets, variables = makefile_parser.parse_makefile(makefile_name, makefile_path) 16 | 17 | # Process imported Makefile contents 18 | 19 | # Use processed data 20 | print("Targets: {}".format(targets)) 21 | print("Variables: {}".format(variables)) 22 | 23 | # Output processed data 24 | 25 | if __name__ == "__main__": 26 | main() 27 | 28 | -------------------------------------------------------------------------------- /src/mkparse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thanatisia/makefile-parser-python/b18033b31ab2ee9663044d471d1d988a69027ddb/src/mkparse/__init__.py -------------------------------------------------------------------------------- /src/mkparse/mkparse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Makefile Parser 3 | """ 4 | import os 5 | import sys 6 | 7 | class Parser(): 8 | """ 9 | Makefile - make build system configuration file - parser 10 | """ 11 | def __init__(self, makefile_name="Makefile", makefile_path="."): 12 | """ 13 | Class Constructor 14 | """ 15 | self.makefile_name = makefile_name 16 | self.makefile_path = makefile_path 17 | 18 | def ast_parse(self, makefile_string_contents=None): 19 | """ 20 | Makefile parser core unit 21 | 22 | :: Params 23 | - makefile_string_contents : Specify the Makefile content body (after splitting by newline) you wish to import and parse into the memory buffer 24 | + Type: String|List 25 | + Default: "" 26 | 27 | :: Output 28 | - targets: Contains the targets/instructions/rules and the attached dependencies and statements 29 | + Type: Dictionary 30 | - Format 31 | { 32 | "target-name" : { 33 | "dependencies" : [your, dependencies, here], 34 | "statements" : [your, statements, here] 35 | } 36 | } 37 | - Key-Value Explanation 38 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 39 | - Key-Value Mappings 40 | - dependencies : Specify a list of all dependencies 41 | - Notes: 42 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 43 | - i.e. 44 | [target-name]: [dependencies ...] 45 | - statements : Specify a list of all rows of statements to write under the target 46 | - variables : Contains the variables and the attached operator (delimiter) and value 47 | + Type: Dictionary 48 | - Format 49 | { 50 | "variable-name" : { 51 | "operator" : "operator (i.e. =|?=|:=)", 52 | "value" : [your, values, here] 53 | } 54 | } 55 | - Key-Value Explanation 56 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 57 | - Key-Value Mappings 58 | - operator : Specify the operator to map the variable to its value string/array/list 59 | + Type: String 60 | - Operator Keyword Types 61 | + '=' 62 | + '?=' 63 | + ':=' 64 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 65 | + Type: List 66 | - comments : Pass the updated global comments list you wish to export (NOTE: Currently unused; for future development plans) 67 | + Type: Dictionary 68 | - Format 69 | { 70 | "line-number" : comment-from-that-line 71 | } 72 | - Key-Value Explanation 73 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 74 | - Key-Value Mappings 75 | - line-number: The line number; this is mapped to the comment stored at that line 76 | """ 77 | # Initialize Variables 78 | targets = {} 79 | variables = {} 80 | curr_target = None 81 | curr_target_name = "" 82 | line_number = 0 83 | comments = {} # Store comments here; map line number to the comment 84 | operator_checklist = ["=", ":=", "?="] 85 | 86 | # Process and perform data validation + sanitization 87 | ## Null-value data validation 88 | if makefile_string_contents != None: 89 | ## Check if content type is string: Split it by newline into a list 90 | if type(makefile_string_contents) == str: 91 | # String is provided 92 | # Split makefile_string into a list 93 | makefile_string_contents = makefile_string_contents.split("\n") 94 | 95 | # Iterate through Makefile list and import 96 | for line in makefile_string_contents: 97 | # Remove comments 98 | # line = line.split('#', 1)[0] 99 | line = line.rstrip() 100 | 101 | # Check if line is empty after removing comments 102 | if not line: 103 | # Line is empty, continue 104 | continue 105 | 106 | # Check if line contains a '#' (a comment) 107 | if line[0] == '#': 108 | # Store all comments 109 | comments[line_number] = line 110 | 111 | # Check if line contains a '=' (defines a variable) 112 | elif '=' in line: 113 | # Check if line contains spaces 114 | has_space = False 115 | for char in line: 116 | if char == ' ': 117 | has_space = True 118 | break 119 | 120 | # Line contains space 121 | if has_space: 122 | # Initialize Variables 123 | operator_idx = -1 124 | operator = "=" 125 | 126 | # Split the '=' to a LHS and RHS 127 | parts = line.split(' ') 128 | 129 | # Validate/Verify parts list is more than or equals to 2 : Name, Operator and Value, value might be empty 130 | if len(parts) >= 2: 131 | # Initialize Variables 132 | variable_value = [] 133 | 134 | # Strip the newline off the first element which is the variable name 135 | variable_name = parts[0].strip() 136 | 137 | # Check variable name for special characters (=, :=, ?=) 138 | for tmp in operator_checklist: 139 | # Obtain position index 140 | tmp_pos_idx = variable_name.find(tmp) 141 | if tmp_pos_idx > -1: 142 | operator_idx = tmp_pos_idx 143 | operator = tmp 144 | 145 | # Obtain Operator 146 | if operator_idx > -1: 147 | # Split parts according to the newly-discovered operator 148 | parts = line.split(operator) 149 | 150 | # variable_name = variable_name[:operator_idx] 151 | variable_name = parts[0].strip() 152 | 153 | # Check if variable value is provided 154 | if len(parts) >= 2: 155 | # Obtain variable value by splitting the string into a list 156 | variable_value = parts[1].split(' ') 157 | else: 158 | operator = parts[1] 159 | 160 | # Check if variable value is provided 161 | if len(parts) >= 3: 162 | # Obtain variable value 163 | variable_value = parts[2:] 164 | 165 | # Map the variable value to the variable name in the entry mapping 166 | variables[variable_name] = {'operator': operator, 'value': variable_value} 167 | else: 168 | # Line does not contain spaces, carry over 169 | 170 | # Initialize Variables 171 | variable_value = [] 172 | operator_idx = -1 173 | operator = "=" 174 | 175 | # Check variable name for special characters (=, :=, ?=) 176 | for tmp in operator_checklist: 177 | # Obtain position index 178 | tmp_pos_idx = line.find(tmp) 179 | if tmp_pos_idx > -1: 180 | operator_idx = tmp_pos_idx 181 | operator = tmp 182 | 183 | # Split the first occurence delimiter to a LHS and RHS 184 | parts = line.split(operator, 1) 185 | 186 | # Validate/Verify parts list is more than or equals to 2 : Name, Operator and Value, value might be empty 187 | # Strip the newline off the first element which is the variable name 188 | variable_name = parts[0].strip() 189 | 190 | # Obtain variable value 191 | variable_value = parts[1:] 192 | 193 | # Map the variable value to the variable name in the entry mapping 194 | variables[variable_name] = {'operator': operator, 'value': variable_value} 195 | 196 | # Check if line contains ':' (defines a target) 197 | elif ':' in line: 198 | # Check if line ends with ':' (does not have any dependencies) 199 | if line.endswith(':'): 200 | # Ends with ':' == there are no dependencies, also is definitely a target 201 | curr_target_name = line.split(':')[0].strip() 202 | 203 | # Initialize a new entry for the current target 204 | targets[curr_target_name] = {"dependencies" : [], "statements" : []} 205 | else: 206 | # Does not end with ':' == there are dependencies 207 | 208 | # Check if line is really a target and not a statement 209 | if not ('\t' in line): 210 | # This is a target with dependencies 211 | 212 | # Split line by ':' 213 | curr_target = line.split(':') 214 | curr_target_name = curr_target[0].strip() 215 | curr_target_dependencies = curr_target[1].strip() 216 | 217 | # Initialize a new entry for the current target 218 | targets[curr_target_name] = {"dependencies" : [], "statements" : []} 219 | 220 | # Null-value validation 221 | if curr_target_dependencies == "": 222 | curr_target_dependencies = None 223 | 224 | # Remove newline 225 | # current_target = line[:-1].rstrip() 226 | 227 | # Check if dependencies are required 228 | if not(curr_target_dependencies == None): 229 | # Are required 230 | # Append dependencies to the current target 231 | targets[curr_target_name]['dependencies'].append(curr_target_dependencies) 232 | 233 | # Not target = statements, append statements to the target 234 | 235 | # Check for empty name 236 | if not (curr_target_name == '') : 237 | # There's tab, a target does not have indentations (means that this is a statement) 238 | 239 | # Store each row of the target's recipe in its own list 240 | targets[curr_target_name]['statements'].append(line.rstrip()) 241 | 242 | # Increment Line Number 243 | line_number += 1 244 | 245 | for curr_target_name,curr_target_values in targets.items(): 246 | # Get current target's dependencies 247 | curr_target_dependencies = curr_target_values["dependencies"] 248 | 249 | # Get current target's statements 250 | curr_target_statements = curr_target_values["statements"] 251 | 252 | # Remove the first line from the list 253 | targets[curr_target_name]['statements'] = curr_target_statements[1:] 254 | 255 | return [targets, variables, comments] 256 | 257 | def parse_makefile(self, makefile_name="Makefile", makefile_path=".") -> list: 258 | """ 259 | Parse Makefile into Python dictionary (Key-Value/HashMap) object 260 | 261 | :: Syntaxes 262 | :: ======= 263 | :: Makefile variables Format: 264 | [variable-name] = [values ...] 265 | 266 | :: Makefile target/rules Format: 267 | [target-name]: [dependencies] 268 | # statements... 269 | 270 | :: Output 271 | - targets: Pass the new targets list you wish to export 272 | + Type: Dictionary 273 | - Format 274 | { 275 | "target-name" : { 276 | "dependencies" : [your, dependencies, here], 277 | "statements" : [your, statements, here] 278 | } 279 | } 280 | - Key-Value Explanation 281 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 282 | - Key-Value Mappings 283 | - dependencies : Specify a list of all dependencies 284 | - Notes: 285 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 286 | - i.e. 287 | [target-name]: [dependencies ...] 288 | - statements : Specify a list of all rows of statements to write under the target 289 | - variables : Pass the new variables list you wish to export 290 | + Type: Dictionary 291 | - Format 292 | { 293 | "variable-name" : { 294 | "operator" : "operator (i.e. =|?=|:=)", 295 | "value" : [your, values, here] 296 | } 297 | } 298 | - Key-Value Explanation 299 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 300 | - Key-Value Mappings 301 | - operator : Specify the operator to map the variable to its value string/array/list 302 | + Type: String 303 | - Operator Keyword Types 304 | + '=' 305 | + '?=' 306 | + ':=' 307 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 308 | + Type: List 309 | - comments : Pass the updated global comments list you wish to export (NOTE: Currently unused; for future development plans) 310 | + Type: Dictionary 311 | - Format 312 | { 313 | "line-number" : comment-from-that-line 314 | } 315 | - Key-Value Explanation 316 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 317 | - Key-Value Mappings 318 | - line-number: The line number; this is mapped to the comment stored at that line 319 | """ 320 | # Initialize Variables 321 | targets = {} 322 | variables = {} 323 | curr_target = None 324 | curr_target_name = "" 325 | line_number = 0 326 | comments = {} # Store comments here; map line number to the comment 327 | operator_checklist = ["=", ":=", "?="] 328 | 329 | # Process and perform data validation + sanitization 330 | 331 | ## Use the default Makefile file name if not provided 332 | if makefile_name == "": 333 | makefile_name = self.makefile_name 334 | 335 | ## Use the default Makefile file path if not provided 336 | if makefile_path == "": 337 | makefile_path = self.makefile_path 338 | 339 | # Try to open the Makefile 340 | try: 341 | with open(os.path.join(makefile_path, makefile_name), 'r') as makefile: 342 | for line in makefile: 343 | # Remove comments 344 | # line = line.split('#', 1)[0] 345 | line = line.rstrip() 346 | 347 | # Check if line is empty after removing comments 348 | if not line: 349 | # Line is empty, continue 350 | continue 351 | 352 | # Check if line contains a '#' (a comment) 353 | if line[0] == '#': 354 | # Store all comments 355 | comments[line_number] = line 356 | 357 | # Check if line contains a '=' (defines a variable) 358 | elif '=' in line: 359 | # Check if line contains spaces 360 | has_space = False 361 | for char in line: 362 | if char == ' ': 363 | has_space = True 364 | break 365 | 366 | # Line contains space 367 | if has_space: 368 | # Initialize Variables 369 | operator_idx = -1 370 | operator = "=" 371 | 372 | # Split the '=' to a LHS and RHS 373 | parts = line.split(' ') 374 | 375 | # Validate/Verify parts list is more than or equals to 2 : Name, Operator and Value, value might be empty 376 | if len(parts) >= 2: 377 | # Initialize Variables 378 | variable_value = [] 379 | 380 | # Strip the newline off the first element which is the variable name 381 | variable_name = parts[0].strip() 382 | 383 | # Check variable name for special characters (=, :=, ?=) 384 | for tmp in operator_checklist: 385 | # Obtain position index 386 | tmp_pos_idx = variable_name.find(tmp) 387 | if tmp_pos_idx > -1: 388 | operator_idx = tmp_pos_idx 389 | operator = tmp 390 | 391 | # Obtain Operator 392 | if operator_idx > -1: 393 | # Split parts according to the newly-discovered operator 394 | parts = line.split(operator) 395 | 396 | # variable_name = variable_name[:operator_idx] 397 | variable_name = parts[0].strip() 398 | 399 | # Check if variable value is provided 400 | if len(parts) >= 2: 401 | # Obtain variable value by splitting the string into a list 402 | variable_value = parts[1].split(' ') 403 | else: 404 | operator = parts[1] 405 | 406 | # Check if variable value is provided 407 | if len(parts) >= 3: 408 | # Obtain variable value 409 | variable_value = parts[2:] 410 | 411 | # Map the variable value to the variable name in the entry mapping 412 | variables[variable_name] = {'operator': operator, 'value': variable_value} 413 | else: 414 | # Line does not contain spaces, carry over 415 | 416 | # Initialize Variables 417 | variable_value = [] 418 | operator_idx = -1 419 | operator = "=" 420 | 421 | # Check variable name for special characters (=, :=, ?=) 422 | for tmp in operator_checklist: 423 | # Obtain position index 424 | tmp_pos_idx = line.find(tmp) 425 | if tmp_pos_idx > -1: 426 | operator_idx = tmp_pos_idx 427 | operator = tmp 428 | 429 | # Split the first occurence delimiter to a LHS and RHS 430 | parts = line.split(operator, 1) 431 | 432 | # Validate/Verify parts list is more than or equals to 2 : Name, Operator and Value, value might be empty 433 | # Strip the newline off the first element which is the variable name 434 | variable_name = parts[0].strip() 435 | 436 | # Obtain variable value 437 | variable_value = parts[1:] 438 | 439 | # Map the variable value to the variable name in the entry mapping 440 | variables[variable_name] = {'operator': operator, 'value': variable_value} 441 | 442 | # Check if line contains ':' (defines a target) 443 | elif ':' in line: 444 | # Check if line ends with ':' (does not have any dependencies) 445 | if line.endswith(':'): 446 | # Ends with ':' == there are no dependencies, also is definitely a target 447 | curr_target_name = line.split(':')[0].strip() 448 | 449 | # Initialize a new entry for the current target 450 | targets[curr_target_name] = {"dependencies" : [], "statements" : []} 451 | else: 452 | # Does not end with ':' == there are dependencies 453 | 454 | # Check if line is really a target and not a statement 455 | if not ('\t' in line): 456 | # This is a target with dependencies 457 | 458 | # Split line by ':' 459 | curr_target = line.split(':') 460 | curr_target_name = curr_target[0].strip() 461 | curr_target_dependencies = curr_target[1].strip() 462 | 463 | # Initialize a new entry for the current target 464 | targets[curr_target_name] = {"dependencies" : [], "statements" : []} 465 | 466 | # Null-value validation 467 | if curr_target_dependencies == "": 468 | curr_target_dependencies = None 469 | 470 | # Remove newline 471 | # current_target = line[:-1].rstrip() 472 | 473 | # Check if dependencies are required 474 | if not(curr_target_dependencies == None): 475 | # Are required 476 | # Append dependencies to the current target 477 | targets[curr_target_name]['dependencies'].append(curr_target_dependencies) 478 | 479 | # Not target = statements, append statements to the target 480 | 481 | # Check for empty name 482 | if not (curr_target_name == '') : 483 | # There's tab, a target does not have indentations (means that this is a statement) 484 | 485 | # Store each row of the target's recipe in its own list 486 | targets[curr_target_name]['statements'].append(line.rstrip()) 487 | 488 | # Increment Line Number 489 | line_number += 1 490 | 491 | for curr_target_name,curr_target_values in targets.items(): 492 | # Get current target's dependencies 493 | curr_target_dependencies = curr_target_values["dependencies"] 494 | 495 | # Get current target's statements 496 | curr_target_statements = curr_target_values["statements"] 497 | 498 | # Remove the first line from the list 499 | targets[curr_target_name]['statements'] = curr_target_statements[1:] 500 | 501 | # Close file after usage 502 | makefile.close() 503 | except FileNotFoundError: 504 | print("Makefile not found.") 505 | 506 | return [targets, variables, comments] 507 | 508 | def parse_makefile_string(self, makefile_string="") -> list: 509 | """ 510 | Parse a Makefile syntax string into Python dictionary (Key-Value/HashMap) object 511 | 512 | :: Params 513 | - makefile_string : Specify the Makefile content body you wish to import and parse into the memory buffer 514 | + Type: String 515 | + Default: "" 516 | 517 | :: Syntaxes 518 | :: ======= 519 | - Makefile variables Format: 520 | ``` 521 | [variable-name] = [values ...] 522 | ``` 523 | 524 | - Makefile target/rules Format: 525 | ``` 526 | [target-name]: [dependencies] 527 | # statements... 528 | ``` 529 | 530 | :: Output 531 | - targets: Pass the new targets list you wish to export 532 | + Type: Dictionary 533 | - Format 534 | { 535 | "target-name" : { 536 | "dependencies" : [your, dependencies, here], 537 | "statements" : [your, statements, here] 538 | } 539 | } 540 | - Key-Value Explanation 541 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 542 | - Key-Value Mappings 543 | - dependencies : Specify a list of all dependencies 544 | - Notes: 545 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 546 | - i.e. 547 | [target-name]: [dependencies ...] 548 | - statements : Specify a list of all rows of statements to write under the target 549 | - variables : Pass the new variables list you wish to export 550 | + Type: Dictionary 551 | - Format 552 | { 553 | "variable-name" : { 554 | "operator" : "operator (i.e. =|?=|:=)", 555 | "value" : [your, values, here] 556 | } 557 | } 558 | - Key-Value Explanation 559 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 560 | - Key-Value Mappings 561 | - operator : Specify the operator to map the variable to its value string/array/list 562 | + Type: String 563 | - Operator Keyword Types 564 | + '=' 565 | + '?=' 566 | + ':=' 567 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 568 | + Type: List 569 | - comments : Pass the updated global comments list you wish to export (NOTE: Currently unused; for future development plans) 570 | + Type: Dictionary 571 | - Format 572 | { 573 | "line-number" : comment-from-that-line 574 | } 575 | - Key-Value Explanation 576 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 577 | - Key-Value Mappings 578 | - line-number: The line number; this is mapped to the comment stored at that line 579 | """ 580 | # Initialize Variables 581 | targets = {} 582 | variables = {} 583 | comments = {} # Store comments here; map line number to the comment 584 | 585 | # Process and perform data validation + sanitization 586 | if makefile_string != "": 587 | # String is provided 588 | # Split makefile_string into a list 589 | makefile_string_contents = makefile_string.split("\n") 590 | 591 | targets, variables, comments = self.ast_parse(makefile_string_contents) 592 | 593 | return [targets, variables, comments] 594 | 595 | def export_Makefile(self, targets:dict, variables:dict, makefile_name="Makefile", makefile_path=".") -> str: 596 | """ 597 | Export the targets and variables list into an output Makefile 598 | 599 | :: Params 600 | - targets: Pass the new targets list you wish to export 601 | + Type: Dictionary 602 | - Format 603 | { 604 | "target-name" : { 605 | "dependencies" : [your, dependencies, here], 606 | "statements" : [your, statements, here] 607 | } 608 | } 609 | - Key-Value Explanation 610 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 611 | - Key-Value Mappings 612 | - dependencies : Specify a list of all dependencies 613 | - Notes: 614 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 615 | - i.e. 616 | [target-name]: [dependencies ...] 617 | - statements : Specify a list of all rows of statements to write under the target 618 | - variables : Pass the new variables list you wish to export 619 | + Type: Dictionary 620 | - Format 621 | { 622 | "variable-name" : { 623 | "operator" : "operator (i.e. =|?=|:=)", 624 | "value" : [your, values, here] 625 | } 626 | } 627 | - Key-Value Explanation 628 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 629 | - Key-Value Mappings 630 | - operator : Specify the operator to map the variable to its value string/array/list 631 | + Type: String 632 | - Operator Keyword Types 633 | + '=' 634 | + '?=' 635 | + ':=' 636 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 637 | + Type: List 638 | - makefile_name : Specify the name of the output Makefile to export to 639 | + Type: String 640 | + Default Value: "Makefile" 641 | - makefile_path : Specify the path containing the output Makefile to export to 642 | + Type: String 643 | + Default Value: "." 644 | 645 | :: Output 646 | - File Name: [makefile_path]/[makefile_name] 647 | - File Type: Makefile 648 | - Format: 649 | ``` 650 | [variable-name] = variable values 651 | [target-name] : your dependencies here 652 | # Instructions/statements 653 | ``` 654 | """ 655 | # Initialize Variables 656 | error_msg = "" 657 | makefile_fullpath = os.path.join(makefile_path, makefile_name) 658 | 659 | # Check if file exists 660 | if not (os.path.isfile(makefile_fullpath)): 661 | # Open file 662 | with open(makefile_fullpath, "a+") as export_Makefile: 663 | # Loop through all variables 664 | for variable_name, variable_mappings in variables.items(): 665 | # Obtain variable map operator 666 | variable_operator = variable_mappings["operator"] 667 | 668 | # Obtain variable values 669 | variable_values = ' '.join(variable_mappings["value"]) 670 | 671 | # Write to Makefile 672 | out_str = "{} {} {}\n".format(variable_name, variable_operator, variable_values) 673 | export_Makefile.write(out_str) 674 | 675 | export_Makefile.write("\n") 676 | 677 | # Loop through all targets 678 | for target_name, target_mappings in targets.items(): 679 | # Obtain target dependencies 680 | target_dependencies = ' '.join(target_mappings["dependencies"]) 681 | 682 | # Obtain target statements 683 | target_statements = '\n'.join(target_mappings["statements"]) 684 | 685 | # Write to Makefile 686 | out_str = "{}: {}\n{}\n".format(target_name, target_dependencies, target_statements) 687 | export_Makefile.write(out_str + "\n") 688 | 689 | # Close file after usage 690 | export_Makefile.close() 691 | else: 692 | error_msg = "Makefile {} already exists".format(makefile_fullpath) 693 | 694 | return error_msg 695 | 696 | def format_makefile_Contents(self, targets=None, variables=None) -> dict: 697 | """ 698 | Format provided makefile targets and variables into content strings 699 | 700 | :: Params 701 | - targets: Specify the Makefile targets to format 702 | + Type: Dictionary 703 | + Default: None 704 | - Format 705 | { 706 | "target-name" : { 707 | "dependencies" : [your, dependencies, here], 708 | "statements" : [your, statements, here] 709 | } 710 | } 711 | - Key-Value Explanation 712 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 713 | - Key-Value Mappings 714 | - dependencies : Specify a list of all dependencies 715 | - Notes: 716 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 717 | - i.e. 718 | [target-name]: [dependencies ...] 719 | - statements : Specify a list of all rows of statements to write under the target 720 | - variables: Specify the Makefile variables to format 721 | + Type: Dictionary 722 | + Default: None 723 | - Format 724 | { 725 | "variable-name" : { 726 | "operator" : "operator (i.e. =|?=|:=)", 727 | "value" : [your, values, here] 728 | } 729 | } 730 | - Key-Value Explanation 731 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 732 | - Key-Value Mappings 733 | - operator : Specify the operator to map the variable to its value string/array/list 734 | + Type: String 735 | - Operator Keyword Types 736 | + '=' 737 | + '?=' 738 | + ':=' 739 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 740 | + Type: List 741 | 742 | :: Output 743 | - contents: Dictionary (key-value) Mapping of the formatted strings, stored in a list of the targets and variables respectively 744 | + Type: Dictionary 745 | - Key-Value mappings 746 | - targets : List of all targets formatted into a printable string 747 | + Type: List 748 | - variables : List of all variables formatted into a printable string 749 | + Type: List 750 | """ 751 | # Initialize Variables 752 | contents = {"targets" : [], "variables" : []} 753 | 754 | # Check if variables is empty 755 | if variables != None: 756 | for var_name, var_values in variables.items(): 757 | # Get variable operator 758 | curr_var_operator = var_values["operator"] 759 | 760 | # Get variable value 761 | curr_var_value = ' '.join(var_values["value"]) 762 | 763 | # Format current variable 764 | curr_out_string = "{} {} {}".format(var_name, curr_var_operator, curr_var_value) 765 | contents["variables"].append(curr_out_string) 766 | 767 | # Check if targets is empty 768 | if targets != None: 769 | for target_name, target_settings in targets.items(): 770 | # Get current target dependencies 771 | curr_target_dependencies = ' '.join(target_settings["dependencies"]) 772 | 773 | # Get current target statements 774 | curr_target_statements = '\n'.join(target_settings["statements"]) 775 | 776 | # Format current variable 777 | curr_out_string = "{}: {}\n{}\n".format(target_name, curr_target_dependencies, curr_target_statements) 778 | contents["targets"].append(curr_out_string) 779 | 780 | return contents 781 | 782 | def trim_contents(self, targets=None, variables=None) -> list: 783 | """ 784 | Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 785 | 786 | :: Params 787 | - targets: Pass the new targets list you wish to export 788 | + Type: Dictionary 789 | - Format 790 | { 791 | "target-name" : { 792 | "dependencies" : [your, dependencies, here], 793 | "statements" : [your, statements, here] 794 | } 795 | } 796 | - Key-Value Explanation 797 | - target-name : Each entry of 'target-name' contains a Makefile build target/instruction/rule 798 | - Key-Value Mappings 799 | - dependencies : Specify a list of all dependencies 800 | - Notes: 801 | - Dependencies are the pre-requisite rules to execute before executing the mapped target 802 | - i.e. 803 | [target-name]: [dependencies ...] 804 | - statements : Specify a list of all rows of statements to write under the target 805 | - variables : Pass the new variables list you wish to export 806 | + Type: Dictionary 807 | - Format 808 | { 809 | "variable-name" : { 810 | "operator" : "operator (i.e. =|?=|:=)", 811 | "value" : [your, values, here] 812 | } 813 | } 814 | - Key-Value Explanation 815 | - variable-name : Each entry of 'variable-name' contains a Makefile variable/ingredient 816 | - Key-Value Mappings 817 | - operator : Specify the operator to map the variable to its value string/array/list 818 | + Type: String 819 | - Operator Keyword Types 820 | + '=' 821 | + '?=' 822 | + ':=' 823 | - value : Specify the value string/array/list (as a list) that you want to map to the variable 824 | + Type: List 825 | """ 826 | # Initialize Variables 827 | 828 | # Check if target is provided 829 | if targets != None: 830 | # Strip all special characters from targets and variables 831 | for target_name, target_mappings in targets.items(): 832 | # Get target dependencies and statements 833 | curr_target_dependencies:list = target_mappings["dependencies"] 834 | curr_target_statements:list = target_mappings["statements"] 835 | 836 | # Loop through dependencies list 837 | for i in range(len(curr_target_dependencies)): 838 | # Get current dependency 839 | curr_dependency = curr_target_dependencies[i] 840 | 841 | # Strip the current dependency 842 | curr_dependency_stripped = curr_dependency.strip() 843 | 844 | # Replace values with stripped dependencies 845 | targets[target_name]["dependencies"][i] = curr_dependency_stripped 846 | 847 | # Loop through statements list 848 | for i in range(len(curr_target_statements)): 849 | # Get current dependency 850 | curr_statement = curr_target_statements[i] 851 | 852 | # Strip the current statement 853 | curr_statement_stripped = curr_statement.strip() 854 | 855 | # Replace values with stripped statements 856 | targets[target_name]["statements"][i] = curr_statement_stripped 857 | 858 | # Check if variables is provided 859 | if variables != None: 860 | # Strip all special characters from targets and variables 861 | for variable_name, variable_mappings in variables.items(): 862 | # Get variable key-values 863 | curr_variable_values:list = variable_mappings["value"] 864 | 865 | # Loop through variable values list 866 | for i in range(len(curr_variable_values)): 867 | # Get current value 868 | curr_value = curr_variable_values[i] 869 | 870 | # Strip the current dependency 871 | curr_value_stripped = curr_value.strip() 872 | 873 | # Replace values with stripped values 874 | variables[variable_name]["value"][i] = curr_value_stripped 875 | 876 | # Check if results is found 877 | if (targets != None) and (variables != None): 878 | return [targets, variables] 879 | elif (targets != None): 880 | return targets 881 | elif (variables != None): 882 | return variables 883 | else: 884 | return [] 885 | 886 | -------------------------------------------------------------------------------- /tests/resources/Makefiles/test.Makefile: -------------------------------------------------------------------------------- 1 | # Test Makefile 2 | 3 | ## Variables/Ingredients 4 | 5 | SHELL := /bin/bash 6 | .PHONY := help 7 | .DEFAULT_RULES := help setup build 8 | 9 | ## Recipe/Targets 10 | help: 11 | ## Display help message 12 | @echo -e "[+] help : Display Help message" 13 | @echo -e "[+] setup : Perform setup" 14 | @echo -e "[+] build : Build from Source" 15 | 16 | setup: 17 | ## Perform setup 18 | 19 | build: setup 20 | ## Build from source 21 | @make -j3 22 | 23 | -------------------------------------------------------------------------------- /tests/unittest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main Runner/Launcher 3 | """ 4 | import os 5 | import sys 6 | from mkparse.mkparse import Parser 7 | 8 | def init(makefile_name="Makefile", makefile_path="."): 9 | """ 10 | Initialize Global Variables 11 | """ 12 | global makefile_parser 13 | makefile_parser = Parser(makefile_name, makefile_path) # Initialize Makefile Parser 14 | 15 | def test_Makefile(makefile_name="Makefile", makefile_path=".") -> list: 16 | # Initialize Variables 17 | 18 | # Import Makefile contents into application runtime 19 | targets, variables, comments = makefile_parser.parse_makefile(makefile_name, makefile_path) 20 | 21 | # Process imported Makefile contents 22 | 23 | # Use processed data 24 | 25 | # Output processed data 26 | return [targets, variables, comments] 27 | 28 | def test_import_makefile_String(makefile_string="", makefile_name="Makefile", makefile_path="."): 29 | # Initialize Variables 30 | 31 | # Import Makefile content string into application runtime 32 | targets, variables, comments = makefile_parser.parse_makefile_string(makefile_string) 33 | 34 | # Process imported Makefile contents 35 | 36 | # Use processed data 37 | 38 | # Output processed data 39 | return [targets, variables, comments] 40 | 41 | def test_export_Makefile(targets, variables, makefile_name="Makefile", makefile_path=".") -> None: 42 | # Initialize Variables 43 | 44 | # Export Makefile dictionaries to Makefile 45 | makefile_parser.export_Makefile(targets, variables, makefile_name, makefile_path) 46 | 47 | # Process imported Makefile contents 48 | 49 | # Use processed data 50 | 51 | # Output processed data 52 | 53 | def test_format_Makefile(targets, variables) -> list: 54 | """ 55 | Testing 'format_makefile_Contents()' for both targets and variables 56 | """ 57 | # Initialize Variables 58 | 59 | # Format Makefile output into formatted string 60 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(targets, variables) 61 | 62 | # Process imported Makefile contents 63 | formatted_makefile_Targets = formatted_makefile_Contents["targets"] 64 | formatted_makefile_Variables = formatted_makefile_Contents["variables"] 65 | 66 | # Use processed data 67 | 68 | # Output processed data 69 | return [formatted_makefile_Targets, formatted_makefile_Variables] 70 | 71 | def test_format_Makefile_target(targets) -> list: 72 | """ 73 | Testing 'format_makefile_Contents()' only for the target 74 | """ 75 | # Initialize Variables 76 | 77 | # Format Makefile output into formatted string 78 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(targets=targets) 79 | 80 | # Process imported Makefile contents 81 | formatted_makefile_Targets = formatted_makefile_Contents["targets"] 82 | 83 | # Use processed data 84 | 85 | # Output processed data 86 | return formatted_makefile_Targets 87 | 88 | def test_format_Makefile_variables(variables) -> list: 89 | """ 90 | Testing 'format_makefile_Contents()' only for the variables 91 | """ 92 | # Initialize Variables 93 | 94 | # Format Makefile output into formatted string 95 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(variables=variables) 96 | 97 | # Process imported Makefile contents 98 | formatted_makefile_Variables = formatted_makefile_Contents["variables"] 99 | 100 | # Use processed data 101 | 102 | # Output processed data 103 | return formatted_makefile_Variables 104 | 105 | def test_trim_makefile_Contents(targets, variables) -> list: 106 | """ 107 | Test trim function 'trim_makefile_contents()' 108 | 109 | Goal: Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 110 | """ 111 | # Initialize Variables 112 | 113 | # Format Makefile output into formatted string 114 | trimmed_targets, trimmed_variables = makefile_parser.trim_contents(targets, variables) 115 | 116 | # Process imported Makefile contents 117 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(trimmed_targets, trimmed_variables) 118 | 119 | # Process imported Makefile contents 120 | formatted_trimmed_Targets = formatted_makefile_Contents["targets"] 121 | formatted_trimmed_Variables = formatted_makefile_Contents["variables"] 122 | 123 | # Use processed data 124 | 125 | # Output processed data 126 | return [formatted_trimmed_Targets, formatted_trimmed_Variables] 127 | 128 | def test_trim_makefile_Targets(targets) -> list: 129 | """ 130 | Test trim function 'trim_makefile_contents()' for targets only 131 | 132 | Goal: Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 133 | """ 134 | # Initialize Variables 135 | 136 | # Format Makefile output into formatted string 137 | trimmed_targets = makefile_parser.trim_contents(targets=targets) 138 | 139 | # Process imported Makefile contents 140 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(targets=trimmed_targets) 141 | 142 | # Process imported Makefile contents 143 | formatted_trimmed_Targets = formatted_makefile_Contents["targets"] 144 | 145 | # Use processed data 146 | 147 | # Output processed data 148 | return formatted_trimmed_Targets 149 | 150 | def test_trim_makefile_Variables(variables) -> list: 151 | """ 152 | Test trim function 'trim_makefile_contents()' for targets only 153 | 154 | Goal: Trim and remove all special characters ("\n", "\t" etc) from the imported file contents 155 | """ 156 | # Initialize Variables 157 | 158 | # Format Makefile output into formatted string 159 | trimmed_variables = makefile_parser.trim_contents(variables=variables) 160 | 161 | # Process imported Makefile contents 162 | formatted_makefile_Contents = makefile_parser.format_makefile_Contents(variables=trimmed_variables) 163 | 164 | # Process imported Makefile contents 165 | formatted_trimmed_Variables = formatted_makefile_Contents["variables"] 166 | 167 | # Use processed data 168 | 169 | # Output processed data 170 | return formatted_trimmed_Variables 171 | 172 | def main(): 173 | """ 174 | Unit Test launcher 175 | """ 176 | # Initialize Variables 177 | exec = sys.argv[0] 178 | argv = sys.argv[1:] 179 | argc = len(argv) 180 | makefile_path = "." 181 | makefile_name = "Makefile" 182 | 183 | # Get Arguments 184 | if argc >= 2: 185 | makefile_path = argv[0] 186 | makefile_name = argv[1] 187 | 188 | init(makefile_name, makefile_path) 189 | 190 | # Test Makefile import 191 | print("Testing Makefile import...") 192 | targets, variables, comments = test_Makefile() 193 | 194 | print("") 195 | 196 | # Test Makefile data formatting 197 | print("Testing Makefile data formatting...") 198 | formatted_makefile_Targets, formatted_makefile_Variables = test_format_Makefile(targets, variables) 199 | print("=========") 200 | print("Variables") 201 | print("=========") 202 | for i in range(len(formatted_makefile_Variables)): 203 | # Get current line 204 | curr_line = formatted_makefile_Variables[i] 205 | # Print 206 | print(curr_line) 207 | 208 | print("") 209 | 210 | print("=======") 211 | print("Targets") 212 | print("=======") 213 | for i in range(len(formatted_makefile_Targets)): 214 | # Get current line 215 | curr_line = formatted_makefile_Targets[i] 216 | # Print 217 | print(curr_line) 218 | 219 | print("") 220 | 221 | # Testing 'format_makefile_Contents()' only for the target 222 | print("Testing 'format_makefile_Contents()' only for the target...") 223 | formatted_makefile_Targets = test_format_Makefile_target(targets) 224 | 225 | print("=======") 226 | print("Targets") 227 | print("=======") 228 | for i in range(len(formatted_makefile_Targets)): 229 | # Get current line 230 | curr_line = formatted_makefile_Targets[i] 231 | # Print 232 | print(curr_line) 233 | 234 | print("") 235 | 236 | # Testing 'format_makefile_Contents()' only for the variables 237 | print("Testing 'format_makefile_Contents()' only for the variables...") 238 | formatted_makefile_Variables = test_format_Makefile_variables(variables) 239 | 240 | print("=========") 241 | print("Variables") 242 | print("=========") 243 | for i in range(len(formatted_makefile_Variables)): 244 | # Get current line 245 | curr_line = formatted_makefile_Variables[i] 246 | # Print 247 | print(curr_line) 248 | 249 | print("") 250 | 251 | # Export Makefile 252 | print("Testing Export Makefile...") 253 | err_msg = test_export_Makefile(targets, variables) 254 | if err_msg != None: 255 | print("Error during exporting: {}".format(err_msg)) 256 | else: 257 | print("Export successful") 258 | 259 | print("") 260 | 261 | # Resetting 262 | print("Resetting...") 263 | targets, variables, comments = test_Makefile() 264 | 265 | print("") 266 | 267 | # Test Makefile trim contents for targets and variables 268 | print("Testing Makefile content trim...") 269 | trimmed_makefile_Targets, trimmed_makefile_Variables = test_trim_makefile_Contents(targets, variables) 270 | print("=========") 271 | print("Variables") 272 | print("=========") 273 | for i in range(len(trimmed_makefile_Variables)): 274 | # Get current line 275 | curr_line = trimmed_makefile_Variables[i] 276 | # Print 277 | print(curr_line) 278 | 279 | print("") 280 | 281 | print("=======") 282 | print("Targets") 283 | print("=======") 284 | for i in range(len(trimmed_makefile_Targets)): 285 | # Get current line 286 | curr_line = trimmed_makefile_Targets[i] 287 | # Print 288 | print(curr_line) 289 | 290 | print("") 291 | 292 | # Resetting 293 | print("Resetting...") 294 | targets, variables, comments = test_Makefile() 295 | 296 | # Test Makefile trim contents for targets only 297 | print("Testing Makefile content trim for Targets only...") 298 | trimmed_makefile_Targets = test_trim_makefile_Targets(targets) 299 | 300 | print("=======") 301 | print("Targets") 302 | print("=======") 303 | for i in range(len(trimmed_makefile_Targets)): 304 | # Get current line 305 | curr_line = trimmed_makefile_Targets[i] 306 | # Print 307 | print(curr_line) 308 | 309 | print("") 310 | 311 | # Resetting 312 | print("Resetting...") 313 | targets, variables, comments = test_Makefile() 314 | 315 | print("") 316 | 317 | # Test Makefile trim contents for variables only 318 | print("Testing Makefile content trim for Variables only...") 319 | trimmed_makefile_Variables = test_trim_makefile_Variables(variables) 320 | print("=========") 321 | print("Variables") 322 | print("=========") 323 | for i in range(len(trimmed_makefile_Variables)): 324 | # Get current line 325 | curr_line = trimmed_makefile_Variables[i] 326 | # Print 327 | print(curr_line) 328 | 329 | print("") 330 | 331 | # Test Makefile string import 332 | print("Testing Makefile string import...") 333 | makefile_str = """# Makefile 334 | # for Building from Source 335 | 336 | ### Build Info 337 | CC = your-cross-compiler (i.e. make|ninja etc) 338 | CFLAGS = your-cross-compilation-flags 339 | DEPENDENCIES = your-dependencies-here 340 | 341 | ### Package Information 342 | PKG_AUTHOR = author-name 343 | PKG_NAME = package-name 344 | BIN_NAME = binaries and executables 345 | SRC_URL = https://github.com/$(PKG_AUTHOR)/$(PKG_NAME) 346 | INSTALL_PATH = installation-path (Default: /usr/local) 347 | CONFIGURE_OPTS = "--prefix=$(INSTALL_PATH)" 348 | 349 | ### System Information 350 | EDITOR=vim 351 | SHELL := /bin/bash 352 | .PHONY := help install-dependencies setup build install uninstall clean enter 353 | .DEFAULT_RULES := help 354 | 355 | ## Recipe/Targets 356 | help: 357 | ## Display help message 358 | @echo -e "[+] help : Display Help message" 359 | @echo -e "[+] install-dependencies : Install system packages" 360 | @echo -e "[+] clone : Clone repository if doesnt exist and initialize submodules" 361 | @echo -e "[+] configure : Configure the repository source files before building" 362 | @echo -e "[+] setup : Setup pre-requisites" 363 | @echo -e "[+] build : Compile/Build everything" 364 | @echo -e "[+] build-all : Build the project from Source" 365 | @echo -e "[+] build-doc : Build the project documentations from Source" 366 | @echo -e "[+] install: Install everything to the host system" 367 | @echo -e "[+] install-bin : Install and move the compiled binary to the host system" 368 | @echo -e "[+] install-html : Install HTML to the host system" 369 | @echo -e "[+] install-doc : Install documentations to the host system" 370 | @echo -e "[+] uninstall : Uninstall and remove the installed files from the host system" 371 | @echo -e "[+] clean : Clean/Remove all temporarily-generated files from repository" 372 | @echo -e "[+] enter : Enter the package repository" 373 | 374 | install-dependencies: 375 | ## Install dependencies 376 | @apt update && apt upgrade && apt install ${DEPENDENCIES} 377 | 378 | clone: 379 | ### Clone repository if doesnt exist and initialize submodules 380 | @test -d ${PKG_NAME} || git clone "${SRC_URL}" && cd ${PKG_NAME}; \\ 381 | # Initialize git submodule contents 382 | ## 2>&1 : Redirect stderr to stdout 383 | echo -e "Initializing git submodules..."; git submodule init 2>&1; \\ 384 | # Update and clone all git submodules recursively 385 | echo -e "Cloning all git submodules..."; git submodule update --recursive 2>&1 386 | 387 | configure: clone 388 | ## Configure the repository source files before building 389 | @cd ${PKG_NAME}; ${CC} configure && ./configure ${CONFIGURE_OPTS} 390 | 391 | setup: clone configure 392 | ## Setup and perform pre-requisites 393 | 394 | build: build-all build-doc 395 | ## Compile/Build everything 396 | 397 | build-all: setup 398 | ## Compile and Build/make the source code into an executable 399 | @cd ${PKG_NAME}; ${CC} ${CFLAGS} all && \\ 400 | echo -e "[+] Compilation Successful." || \\ 401 | echo -e "[-] Compilation Error." 402 | 403 | build-doc: setup 404 | ## Compile and Build documentations 405 | @cd ${PKG_NAME}; ${CC} ${CFLAGS} doc && \\ 406 | echo -e "[+] Compilation Successful." || \\ 407 | echo -e "[-] Compilation Error." 408 | 409 | install: install-bin install-html install-doc 410 | ## Install everything to the host system 411 | 412 | install-bin: clone 413 | ## Install and move the compiled binary to the host system 414 | @cd ${PKG_NAME}; ${CC} ${CFLAGS} install && \\ 415 | echo -e "[+] Executable Installation Successful." || \\ 416 | echo -e "[-] Executable Installation Error." 417 | 418 | install-html: clone 419 | ## Install HTML to the host system 420 | @cd ${PKG_NAME}; ${CC} ${CFLAGS} install-html && \\ 421 | echo -e "[+] HTML Documentations Installation Successful." || \\ 422 | echo -e "[-] HTML Documentations Installation Error." 423 | 424 | install-doc: clone 425 | ## Install documentations to the host system 426 | @cd ${PKG_NAME}; ${CC} ${CFLAGS} install-doc && \\ 427 | echo -e "[+] Documentations Installation Successful." || \\ 428 | echo -e "[+] Documentations Installation Error." 429 | 430 | uninstall: clone 431 | ## Uninstall and remove installed files from the host system 432 | ### Uninstall a binary 433 | @rm /usr/local/bin/{binaries,here,...} 434 | ### Uninstall a directory 435 | @test -d /usr/local/[directory] && rm -r /usr/local/[directory] 436 | ### Uninstall manuals (man1) 437 | @rm /usr/local/share/man/man1/[manual].1 438 | ### Uninstall manuals (man3) 439 | @rm /usr/local/share/man/man3/[manual].3pm 440 | ### Uninstall manuals (man5) 441 | @rm /usr/local/share/man/man5/[manual]* 442 | ### Uninstall manuals (man7) 443 | @rm /usr/local/share/man/man7/[manual]* 444 | 445 | clean: clone 446 | ## Cleanup and remove temporary files generated during compilation 447 | @cd ${PKG_NAME}; ${CC} clean && \\ 448 | echo -e "[+] Cleanup Successful." || \\ 449 | echo -e "[-] Cleanup Error." 450 | 451 | enter: clone 452 | ## Enter the package repository 453 | @cd ${PKG_NAME}; 454 | """ 455 | targets, variables, comments = test_import_makefile_String(makefile_str, makefile_name, makefile_path) 456 | print("Targets: {}".format(targets)) 457 | print("Variables: {}".format(variables)) 458 | # Export the generated Makefile 459 | test_export_Makefile(targets, variables, makefile_name="test-export.Makefile") 460 | # Format the generated Makefile into human-readable standard output 461 | formatted_makefile_Targets, formatted_makefile_Variables = test_format_Makefile(targets, variables) 462 | print("=========") 463 | print("Variables") 464 | print("=========") 465 | for i in range(len(formatted_makefile_Variables)): 466 | # Get current line 467 | curr_line = formatted_makefile_Variables[i] 468 | # Print 469 | print(curr_line) 470 | 471 | print("") 472 | 473 | print("=======") 474 | print("Targets") 475 | print("=======") 476 | for i in range(len(formatted_makefile_Targets)): 477 | # Get current line 478 | curr_line = formatted_makefile_Targets[i] 479 | # Print 480 | print(curr_line) 481 | 482 | print("") 483 | 484 | if __name__ == "__main__": 485 | main() 486 | 487 | --------------------------------------------------------------------------------