[{"data":1,"prerenderedAt":2727},["ShallowReactive",2],{"\u002Fblogs\u002Fbuilding-cli-apps-with-argparse-from-the-standard-library":3},{"id":4,"title":5,"body":6,"coverImage":2715,"description":2718,"extension":2719,"meta":2720,"navigation":210,"path":2721,"publishedOn":2722,"seo":2723,"sitemap":2724,"stem":2725,"__hash__":2726},"content\u002Fblogs\u002Fbuilding-cli-apps-with-argparse-from-the-standard-library.md","Building CLI applications with Python's argparse module.",{"type":7,"value":8,"toc":2708},"minimark",[9,13,35,52,58,63,69,88,131,134,147,192,195,272,275,292,295,309,322,325,338,341,414,417,433,443,448,451,455,465,471,488,491,494,654,660,705,718,721,745,1091,1097,1165,1181,1219,1245,1257,1260,1264,1267,1293,1296,1307,1318,1324,1449,1461,1470,1475,1796,1810,1813,1817,1820,1826,1926,1936,1939,2601,2608,2629,2632,2659,2663,2669,2672,2675,2701,2704],[10,11,12],"p",{},"If you've automated repetitive tasks with a Python script that has grown more\ncomplex over time, you've probably considered turning it into a CLI tool for\neasier sharing. I'm in the same boat right now, exploring the best ways to\ncreate and distribute these scripts!",[10,14,15,16,23,24,29,30,34],{},"While CLI frameworks like ",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Ftyper.tiangolo.com\u002F",[21],"nofollow","Typer"," and\n",[17,25,28],{"href":26,"rel":27},"https:\u002F\u002Fclick.palletsprojects.com\u002Fen\u002Fstable\u002F",[21],"Click"," exist, I prefer not to\nrely on third-party dependencies for simple applications. Avoiding extra\ndependencies keeps the script portable since it only needs the Python\ninterpreter installed and ready to use. That's why the ",[31,32,33],"code",{},"argparse"," module from\nPython's standard library is perfect for my needs.",[10,36,37,38,40,41,46,47,51],{},"This article is a brief guide to the ",[31,39,33],{}," module from the Python standard\nlibrary. For hands-on experience building real-world projects, I'll share\nimplementation details of my\n",[17,42,45],{"href":43,"rel":44},"https:\u002F\u002Fgithub.com\u002FJarmos-san\u002Fdotfiles\u002Fblob\u002Fmain\u002Fdotfiles\u002F.local\u002Fbin\u002Fmkblog",[21],"mkblog","\nscript. I use this script to generate boilerplate Markdown files from a\ntemplate, automating the process of creating new blog posts on my website. On a\nside note, it is ",[48,49,50],"strong",{},"STRONGLY"," recommended to read the official documentation of\nthe module to learn more about it. This article only briefly touches over some\nof the important mechanisms of the module and it should be enough for most\ngeneric everyday use cases.",[10,53,54,57],{},[48,55,56],{},"NOTE",": The instructions and code in this post are written for Unix-like\nsystems (Debian\u002FUbuntu, macOS, etc.). There's no guarantee of similar behavior\nwhen running the code on other systems, especially Windows.",[59,60,62],"h2",{"id":61},"understanding-pythons-cli-argument-parsing","Understanding Python's CLI Argument Parsing",[10,64,65,66,68],{},"If you check out the ",[31,67,45],{}," script linked above, you'll notice a few things:",[70,71,72,76,85],"ol",{},[73,74,75],"li",{},"The script has no extension-Unix-like systems don't require one.",[73,77,78,79,84],{},"The ",[17,80,83],{"href":81,"rel":82},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FShebang_(Unix)",[21],"shebang"," line at the top\npoints to the Python interpreter that executes the script.",[73,86,87],{},"Scroll to the bottom and you'll see this conditional statement:",[89,90,95],"pre",{"className":91,"code":92,"filename":45,"language":93,"meta":94,"style":94},"language-python shiki shiki-themes everforest-dark","if __name__ == \"__main__\":\n    main()\n","python","",[31,96,97,121],{"__ignoreMap":94},[98,99,102,106,110,114,118],"span",{"class":100,"line":101},"line",1,[98,103,105],{"class":104},"safYi","if",[98,107,109],{"class":108},"sY0Nt"," __name__ ",[98,111,113],{"class":112},"sDOmQ","==",[98,115,117],{"class":116},"sySyC"," \"__main__\"",[98,119,120],{"class":108},":\n",[98,122,124,128],{"class":100,"line":123},2,[98,125,127],{"class":126},"sRC7j","    main",[98,129,130],{"class":108},"()\n",[10,132,133],{},"This statement is a standard practice in the Python community-it defers\nexecution of an \"entrypoint\" function to the script. The exact details deserve a\nseparate blog post, so I'll skip them for now.",[10,135,136,137,139,140,142,143,146],{},"Before proceeding, create a file named ",[31,138,45],{}," (without any file extension) and\nfollow along for hands-on experience. Once you've created the ",[31,141,45],{}," file, add\nthe shebang at the top, followed by the conditional statement shown above. Then\ndefine the following ",[31,144,145],{},"main()"," function right above the conditional statement:",[89,148,150],{"className":91,"code":149,"filename":45,"language":93,"meta":94,"style":94},"def main() -> None:\n    '''Entrypoint of the script.'''\n    print(\"Hello World!\")\n",[31,151,152,172,177],{"__ignoreMap":94},[98,153,154,157,160,163,166,170],{"class":100,"line":101},[98,155,156],{"class":104},"def",[98,158,159],{"class":126}," main",[98,161,162],{"class":108},"()",[98,164,165],{"class":108}," ->",[98,167,169],{"class":168},"sFWo_"," None",[98,171,120],{"class":108},[98,173,174],{"class":100,"line":123},[98,175,176],{"class":116},"    '''Entrypoint of the script.'''\n",[98,178,180,183,186,189],{"class":100,"line":179},3,[98,181,182],{"class":126},"    print",[98,184,185],{"class":108},"(",[98,187,188],{"class":116},"\"Hello World!\"",[98,190,191],{"class":108},")\n",[10,193,194],{},"With the contents added to the file, its contents should look similar to this\nand it'll be your Python script from now on.",[89,196,198],{"className":91,"code":197,"filename":45,"language":93,"meta":94,"style":94},"#!\u002Fusr\u002Fbin\u002Fenv python3\n\ndef main() -> None:\n    '''Entrypoint of the script.'''\n    print(\"Hello World!\")\n\n\nif __name__ == \"__main__\":\n    main()\n",[31,199,200,206,212,226,231,242,247,252,265],{"__ignoreMap":94},[98,201,202],{"class":100,"line":101},[98,203,205],{"class":204},"s67c6","#!\u002Fusr\u002Fbin\u002Fenv python3\n",[98,207,208],{"class":100,"line":123},[98,209,211],{"emptyLinePlaceholder":210},true,"\n",[98,213,214,216,218,220,222,224],{"class":100,"line":179},[98,215,156],{"class":104},[98,217,159],{"class":126},[98,219,162],{"class":108},[98,221,165],{"class":108},[98,223,169],{"class":168},[98,225,120],{"class":108},[98,227,229],{"class":100,"line":228},4,[98,230,176],{"class":116},[98,232,234,236,238,240],{"class":100,"line":233},5,[98,235,182],{"class":126},[98,237,185],{"class":108},[98,239,188],{"class":116},[98,241,191],{"class":108},[98,243,245],{"class":100,"line":244},6,[98,246,211],{"emptyLinePlaceholder":210},[98,248,250],{"class":100,"line":249},7,[98,251,211],{"emptyLinePlaceholder":210},[98,253,255,257,259,261,263],{"class":100,"line":254},8,[98,256,105],{"class":104},[98,258,109],{"class":108},[98,260,113],{"class":112},[98,262,117],{"class":116},[98,264,120],{"class":108},[98,266,268,270],{"class":100,"line":267},9,[98,269,127],{"class":126},[98,271,130],{"class":108},[10,273,274],{},"Invoking the script in an interactive shell like this - .\u002Fmkblog will output the\nfollowing message:",[89,276,280],{"className":277,"code":278,"language":279,"meta":94,"style":94},"language-shell shiki shiki-themes everforest-dark",".\u002Fmkblog\n# Output: \"Hello World!\"\n","shell",[31,281,282,287],{"__ignoreMap":94},[98,283,284],{"class":100,"line":101},[98,285,286],{"class":126},".\u002Fmkblog\n",[98,288,289],{"class":100,"line":123},[98,290,291],{"class":204},"# Output: \"Hello World!\"\n",[10,293,294],{},"If you can reproduce this behavior, your script is working as expected.",[10,296,297,298,301,302,304,305,308],{},"Now let's learn about the ",[31,299,300],{},"sys.argv"," variable. Python uses this low-level API to\nprovide a high-level interface for building typical CLI applications. According\nto the Python documentation, ",[31,303,300],{}," is a list of command-line arguments\npassed to the script. The first element, ",[31,306,307],{},"sys.argv[0]",", is the script name:",[310,311,312],"blockquote",{},[10,313,314],{},[315,316,317,318,321],"em",{},"The list of command line arguments passed to a Python script. ",[31,319,320],{},"argv[0]"," is\nthe script name (it is operating system dependent whether this is a full\npathname or not).",[10,323,324],{},"In other words, if we executed the script like this:",[89,326,328],{"className":277,"code":327,"language":279,"meta":94,"style":94},".\u002Fmkblog \"Hello World\"\n",[31,329,330],{"__ignoreMap":94},[98,331,332,335],{"class":100,"line":101},[98,333,334],{"class":126},".\u002Fmkblog",[98,336,337],{"class":126}," \"Hello World\"\n",[10,339,340],{},"You can access the arguments passed to the script and do something with them! To\ntest it out, let's edit our script with the following changes:",[89,342,344],{"className":91,"code":343,"filename":45,"language":93,"meta":94,"style":94},"# ... truncated contents of the file\n\nimport sys\n\ndef main() -> None:\n    '''Entrypoint of the script.'''\n    print(sys.argv[1])\n\n# ... truncated contents of the file\n",[31,345,346,351,355,364,368,382,386,406,410],{"__ignoreMap":94},[98,347,348],{"class":100,"line":101},[98,349,350],{"class":204},"# ... truncated contents of the file\n",[98,352,353],{"class":100,"line":123},[98,354,211],{"emptyLinePlaceholder":210},[98,356,357,361],{"class":100,"line":179},[98,358,360],{"class":359},"s9lfW","import",[98,362,363],{"class":108}," sys\n",[98,365,366],{"class":100,"line":228},[98,367,211],{"emptyLinePlaceholder":210},[98,369,370,372,374,376,378,380],{"class":100,"line":233},[98,371,156],{"class":104},[98,373,159],{"class":126},[98,375,162],{"class":108},[98,377,165],{"class":108},[98,379,169],{"class":168},[98,381,120],{"class":108},[98,383,384],{"class":100,"line":244},[98,385,176],{"class":116},[98,387,388,390,393,397,400,403],{"class":100,"line":249},[98,389,182],{"class":126},[98,391,392],{"class":108},"(sys",[98,394,396],{"class":395},"s3WQq",".",[98,398,399],{"class":108},"argv[",[98,401,402],{"class":359},"1",[98,404,405],{"class":108},"])\n",[98,407,408],{"class":100,"line":254},[98,409,211],{"emptyLinePlaceholder":210},[98,411,412],{"class":100,"line":267},[98,413,350],{"class":204},[10,415,416],{},"Executing the script now produces the following output:",[89,418,420],{"className":277,"code":419,"language":279,"meta":94,"style":94},".\u002Fmkblog \"Hello World\"\n# Output: \"Hello World\"\n",[31,421,422,428],{"__ignoreMap":94},[98,423,424,426],{"class":100,"line":101},[98,425,334],{"class":126},[98,427,337],{"class":126},[98,429,430],{"class":100,"line":123},[98,431,432],{"class":204},"# Output: \"Hello World\"\n",[10,434,435,436,439,440,442],{},"If you've used ",[48,437,438],{},"any"," CLI applications before, you're familiar with their\npositional arguments and options\u002Fflags that change behavior and output. Scaling\nup an application with many arguments and flags using only ",[31,441,300],{}," becomes\nextremely verbose and error-prone.",[10,444,78,445,447],{},[31,446,33],{}," module solves this problem by providing useful abstractions for\ncomplex argument parsing. Beyond parsing, the module offers a helpful\nout-of-the-box experience for building CLI applications with minimal\nconfiguration.",[10,449,450],{},"In the next section we'll lay the foundations of our CLI application (or rather\nthe script!) and then improve it with additional feature updates.",[59,452,454],{"id":453},"setting-up-the-interface","Setting up the Interface",[10,456,457,458,461,462,464],{},"Almost all CLI applications will provide a ",[31,459,460],{},"--help"," flag to print the tool's\nusage guide and documentation. Fortunately for us, the ",[31,463,33],{}," module\nprovides the interface to enable this functionality without any extra code\nwritten for it! So before we start using the module, lets check out the\ndifference in functionality without and then with using the module.",[10,466,467,468,470],{},"If you ran the script as-is right now and passed a ",[31,469,460],{}," flag to it, you'll\nsee the following output:",[89,472,474],{"className":277,"code":473,"language":279,"meta":94,"style":94},".\u002Fmkblog --help\n# Output: \"--help\"\n",[31,475,476,483],{"__ignoreMap":94},[98,477,478,480],{"class":100,"line":101},[98,479,334],{"class":126},[98,481,482],{"class":116}," --help\n",[98,484,485],{"class":100,"line":123},[98,486,487],{"class":204},"# Output: \"--help\"\n",[10,489,490],{},"Not the expected behaviour, right?",[10,492,493],{},"An ideal CLI application should instead provide a helpful usage guide as an\noutput. So let's implement that functionality as a baseline to check whether our\nscript is in a working condition:",[89,495,497],{"className":91,"code":496,"filename":45,"language":93,"meta":94,"style":94},"#!\u002Fusr\u002Fbin\u002Fenv python3\n\nfrom argparse import ArgumentParser, Namespace\n\n\ndef parse_args() -> Namespace:\n    \"\"\"Parse and return the arguments\u002Foptions of the script.\"\"\"\n    parser = ArgumentParser(description=\"generate a blog template\")\n    return parser.parse_args()\n\n\ndef main() -> None:\n    \"\"\"Entrypoint of the script.\"\"\"\n    parse_args()\n\n\nif __name__ == \"__main__\":\n    main()\n",[31,498,499,503,507,520,524,528,544,549,570,585,590,595,610,616,624,629,634,647],{"__ignoreMap":94},[98,500,501],{"class":100,"line":101},[98,502,205],{"class":204},[98,504,505],{"class":100,"line":123},[98,506,211],{"emptyLinePlaceholder":210},[98,508,509,512,515,517],{"class":100,"line":179},[98,510,511],{"class":359},"from",[98,513,514],{"class":108}," argparse ",[98,516,360],{"class":359},[98,518,519],{"class":108}," ArgumentParser, Namespace\n",[98,521,522],{"class":100,"line":228},[98,523,211],{"emptyLinePlaceholder":210},[98,525,526],{"class":100,"line":233},[98,527,211],{"emptyLinePlaceholder":210},[98,529,530,532,535,537,539,542],{"class":100,"line":244},[98,531,156],{"class":104},[98,533,534],{"class":126}," parse_args",[98,536,162],{"class":108},[98,538,165],{"class":108},[98,540,541],{"class":126}," Namespace",[98,543,120],{"class":108},[98,545,546],{"class":100,"line":249},[98,547,548],{"class":116},"    \"\"\"Parse and return the arguments\u002Foptions of the script.\"\"\"\n",[98,550,551,554,557,560,563,565,568],{"class":100,"line":254},[98,552,553],{"class":108},"    parser ",[98,555,556],{"class":112},"=",[98,558,559],{"class":126}," ArgumentParser",[98,561,562],{"class":108},"(description",[98,564,556],{"class":112},[98,566,567],{"class":116},"\"generate a blog template\"",[98,569,191],{"class":108},[98,571,572,575,578,580,583],{"class":100,"line":267},[98,573,574],{"class":104},"    return",[98,576,577],{"class":108}," parser",[98,579,396],{"class":395},[98,581,582],{"class":126},"parse_args",[98,584,130],{"class":108},[98,586,588],{"class":100,"line":587},10,[98,589,211],{"emptyLinePlaceholder":210},[98,591,593],{"class":100,"line":592},11,[98,594,211],{"emptyLinePlaceholder":210},[98,596,598,600,602,604,606,608],{"class":100,"line":597},12,[98,599,156],{"class":104},[98,601,159],{"class":126},[98,603,162],{"class":108},[98,605,165],{"class":108},[98,607,169],{"class":168},[98,609,120],{"class":108},[98,611,613],{"class":100,"line":612},13,[98,614,615],{"class":116},"    \"\"\"Entrypoint of the script.\"\"\"\n",[98,617,619,622],{"class":100,"line":618},14,[98,620,621],{"class":126},"    parse_args",[98,623,130],{"class":108},[98,625,627],{"class":100,"line":626},15,[98,628,211],{"emptyLinePlaceholder":210},[98,630,632],{"class":100,"line":631},16,[98,633,211],{"emptyLinePlaceholder":210},[98,635,637,639,641,643,645],{"class":100,"line":636},17,[98,638,105],{"class":104},[98,640,109],{"class":108},[98,642,113],{"class":112},[98,644,117],{"class":116},[98,646,120],{"class":108},[98,648,650,652],{"class":100,"line":649},18,[98,651,127],{"class":126},[98,653,130],{"class":108},[10,655,656,657,659],{},"Invoking the script now with the ",[31,658,460],{}," flag immediately provides some helpful\nusage guide as shown below:",[89,661,663],{"className":277,"code":662,"language":279,"meta":94,"style":94},".\u002Fmkblog --help\n# Output:\n# usage: mkblog [-h]\n#\n# generate a blog template.\n#\n# options:\n#  -h, --help            show this help message and exit\n",[31,664,665,671,676,681,686,691,695,700],{"__ignoreMap":94},[98,666,667,669],{"class":100,"line":101},[98,668,334],{"class":126},[98,670,482],{"class":116},[98,672,673],{"class":100,"line":123},[98,674,675],{"class":204},"# Output:\n",[98,677,678],{"class":100,"line":179},[98,679,680],{"class":204},"# usage: mkblog [-h]\n",[98,682,683],{"class":100,"line":228},[98,684,685],{"class":204},"#\n",[98,687,688],{"class":100,"line":233},[98,689,690],{"class":204},"# generate a blog template.\n",[98,692,693],{"class":100,"line":244},[98,694,685],{"class":204},[98,696,697],{"class":100,"line":249},[98,698,699],{"class":204},"# options:\n",[98,701,702],{"class":100,"line":254},[98,703,704],{"class":204},"#  -h, --help            show this help message and exit\n",[10,706,707,708,711,712,714,715,717],{},"Our script now resembles a typical CLI application with the addition of ",[48,709,710],{},"ONLY","\na few lines of code! Without the ",[31,713,33],{}," module we would have had to resort\nto some complex and manual argument parsing using the ",[31,716,300],{}," variable. The\nhelp message, the formatting of the command output was all internally processed\nand handled by the module itself and that is quite nifty isn't it?",[10,719,720],{},"Moving on, our application now has an help guide so let's now try adding some\npositional arguments and few more flags to manipulate the data passed as\narguments to our app.",[10,722,723,724,727,728,733,734,737,738,23,741,744],{},"We can add the positional arguments and optional flags using the\n",[31,725,726],{},"argparse.ArgumentParser.add_argument()"," method (see\n",[17,729,732],{"href":730,"rel":731},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fargparse.html#argparse.ArgumentParser.add_argument",[21],"the docs","\nto learn more). So, to implement a ",[31,735,736],{},"title"," , the optional ",[31,739,740],{},"--draft",[31,742,743],{},"--output"," flags, we can update our source code with these changes:",[89,746,748],{"className":91,"code":747,"filename":45,"language":93,"meta":94,"style":94},"#!\u002Fusr\u002Fbin\u002Fenv python3\n\nfrom argparse import ArgumentParser, Namespace\n\n\ndef parse_args() -> Namespace:\n    \"\"\"Parse and return the arguments\u002Foptions of the script.\"\"\"\n    parser = ArgumentParser(description=\"generate a blog template\")\n\n    blog_dir = pathlib.Path().home() \u002F \"blogposts\"\n\n    parser.add_agument(\"title\", type=str, help=\"the title of the blog post\")\n    parser.add_argument(\n        \"-d\",\n            \"--draft\",\n            action=\"store_true\",\n            help=\"create a draft blogpost, defaults to 'True'\"\n        )\n        parser.add_argument(\n            \"-o\",\n            \"--output\",\n            type=pathlib.Path,\n            default=blog_dir\n            help=\"create a draft blogpost, defaults to 'True'\"\n        )\n\n    return parser.parse_args()\n\n\ndef main() -> None:\n    \"\"\"Entrypoint of the script.\"\"\"\n    parse_args()\n\n\nif __name__ == \"__main__\":\n    main()\n",[31,749,750,754,758,768,772,776,790,794,810,814,845,849,883,895,903,910,922,932,937,949,957,965,981,992,1001,1006,1011,1024,1029,1034,1049,1054,1061,1066,1071,1084],{"__ignoreMap":94},[98,751,752],{"class":100,"line":101},[98,753,205],{"class":204},[98,755,756],{"class":100,"line":123},[98,757,211],{"emptyLinePlaceholder":210},[98,759,760,762,764,766],{"class":100,"line":179},[98,761,511],{"class":359},[98,763,514],{"class":108},[98,765,360],{"class":359},[98,767,519],{"class":108},[98,769,770],{"class":100,"line":228},[98,771,211],{"emptyLinePlaceholder":210},[98,773,774],{"class":100,"line":233},[98,775,211],{"emptyLinePlaceholder":210},[98,777,778,780,782,784,786,788],{"class":100,"line":244},[98,779,156],{"class":104},[98,781,534],{"class":126},[98,783,162],{"class":108},[98,785,165],{"class":108},[98,787,541],{"class":126},[98,789,120],{"class":108},[98,791,792],{"class":100,"line":249},[98,793,548],{"class":116},[98,795,796,798,800,802,804,806,808],{"class":100,"line":254},[98,797,553],{"class":108},[98,799,556],{"class":112},[98,801,559],{"class":126},[98,803,562],{"class":108},[98,805,556],{"class":112},[98,807,567],{"class":116},[98,809,191],{"class":108},[98,811,812],{"class":100,"line":267},[98,813,211],{"emptyLinePlaceholder":210},[98,815,816,819,821,824,826,829,831,833,836,839,842],{"class":100,"line":587},[98,817,818],{"class":108},"    blog_dir ",[98,820,556],{"class":112},[98,822,823],{"class":108}," pathlib",[98,825,396],{"class":395},[98,827,828],{"class":126},"Path",[98,830,162],{"class":108},[98,832,396],{"class":395},[98,834,835],{"class":126},"home",[98,837,838],{"class":108},"() ",[98,840,841],{"class":112},"\u002F",[98,843,844],{"class":116}," \"blogposts\"\n",[98,846,847],{"class":100,"line":592},[98,848,211],{"emptyLinePlaceholder":210},[98,850,851,854,856,859,861,864,867,869,873,876,878,881],{"class":100,"line":597},[98,852,853],{"class":108},"    parser",[98,855,396],{"class":395},[98,857,858],{"class":126},"add_agument",[98,860,185],{"class":108},[98,862,863],{"class":116},"\"title\"",[98,865,866],{"class":108},", type",[98,868,556],{"class":112},[98,870,872],{"class":871},"sP-ue","str",[98,874,875],{"class":108},", help",[98,877,556],{"class":112},[98,879,880],{"class":116},"\"the title of the blog post\"",[98,882,191],{"class":108},[98,884,885,887,889,892],{"class":100,"line":612},[98,886,853],{"class":108},[98,888,396],{"class":395},[98,890,891],{"class":126},"add_argument",[98,893,894],{"class":108},"(\n",[98,896,897,900],{"class":100,"line":618},[98,898,899],{"class":116},"        \"-d\"",[98,901,902],{"class":108},",\n",[98,904,905,908],{"class":100,"line":626},[98,906,907],{"class":116},"            \"--draft\"",[98,909,902],{"class":108},[98,911,912,915,917,920],{"class":100,"line":631},[98,913,914],{"class":108},"            action",[98,916,556],{"class":112},[98,918,919],{"class":116},"\"store_true\"",[98,921,902],{"class":108},[98,923,924,927,929],{"class":100,"line":636},[98,925,926],{"class":108},"            help",[98,928,556],{"class":112},[98,930,931],{"class":116},"\"create a draft blogpost, defaults to 'True'\"\n",[98,933,934],{"class":100,"line":649},[98,935,936],{"class":108},"        )\n",[98,938,940,943,945,947],{"class":100,"line":939},19,[98,941,942],{"class":108},"        parser",[98,944,396],{"class":395},[98,946,891],{"class":126},[98,948,894],{"class":108},[98,950,952,955],{"class":100,"line":951},20,[98,953,954],{"class":116},"            \"-o\"",[98,956,902],{"class":108},[98,958,960,963],{"class":100,"line":959},21,[98,961,962],{"class":116},"            \"--output\"",[98,964,902],{"class":108},[98,966,968,971,973,976,978],{"class":100,"line":967},22,[98,969,970],{"class":108},"            type",[98,972,556],{"class":112},[98,974,975],{"class":108},"pathlib",[98,977,396],{"class":395},[98,979,980],{"class":108},"Path,\n",[98,982,984,987,989],{"class":100,"line":983},23,[98,985,986],{"class":108},"            default",[98,988,556],{"class":112},[98,990,991],{"class":108},"blog_dir\n",[98,993,995,997,999],{"class":100,"line":994},24,[98,996,926],{"class":108},[98,998,556],{"class":112},[98,1000,931],{"class":116},[98,1002,1004],{"class":100,"line":1003},25,[98,1005,936],{"class":108},[98,1007,1009],{"class":100,"line":1008},26,[98,1010,211],{"emptyLinePlaceholder":210},[98,1012,1014,1016,1018,1020,1022],{"class":100,"line":1013},27,[98,1015,574],{"class":104},[98,1017,577],{"class":108},[98,1019,396],{"class":395},[98,1021,582],{"class":126},[98,1023,130],{"class":108},[98,1025,1027],{"class":100,"line":1026},28,[98,1028,211],{"emptyLinePlaceholder":210},[98,1030,1032],{"class":100,"line":1031},29,[98,1033,211],{"emptyLinePlaceholder":210},[98,1035,1037,1039,1041,1043,1045,1047],{"class":100,"line":1036},30,[98,1038,156],{"class":104},[98,1040,159],{"class":126},[98,1042,162],{"class":108},[98,1044,165],{"class":108},[98,1046,169],{"class":168},[98,1048,120],{"class":108},[98,1050,1052],{"class":100,"line":1051},31,[98,1053,615],{"class":116},[98,1055,1057,1059],{"class":100,"line":1056},32,[98,1058,621],{"class":126},[98,1060,130],{"class":108},[98,1062,1064],{"class":100,"line":1063},33,[98,1065,211],{"emptyLinePlaceholder":210},[98,1067,1069],{"class":100,"line":1068},34,[98,1070,211],{"emptyLinePlaceholder":210},[98,1072,1074,1076,1078,1080,1082],{"class":100,"line":1073},35,[98,1075,105],{"class":104},[98,1077,109],{"class":108},[98,1079,113],{"class":112},[98,1081,117],{"class":116},[98,1083,120],{"class":108},[98,1085,1087,1089],{"class":100,"line":1086},36,[98,1088,127],{"class":126},[98,1090,130],{"class":108},[10,1092,1093,1094,1096],{},"The changes introduced to our application is now quite verbose but we'll attempt\nto understand the code soon enough. Before that, try invoking the script's\n",[31,1095,460],{}," flag now and you will see an updated output message:",[89,1098,1100],{"className":277,"code":1099,"language":279,"meta":94,"style":94},".\u002Fmkblog --help\n# Output:\n# usage: mkblog [-h]\n#\n# generate a blog template.\n#\n# positional arguments:\n# title                  the title of the blog post\n#\n# options:\n#  -h, --help            show this help message and exit\n#  -d, --draft           create a draft blog post, defaults to 'True'\n#  -o, --output OUTPUT   directory to save the Markdown file at, defaults to\n#                        \"\u002Fhome\u002Fjohndoe\u002Fblogposts\"\n",[31,1101,1102,1108,1112,1116,1120,1124,1128,1133,1138,1142,1146,1150,1155,1160],{"__ignoreMap":94},[98,1103,1104,1106],{"class":100,"line":101},[98,1105,334],{"class":126},[98,1107,482],{"class":116},[98,1109,1110],{"class":100,"line":123},[98,1111,675],{"class":204},[98,1113,1114],{"class":100,"line":179},[98,1115,680],{"class":204},[98,1117,1118],{"class":100,"line":228},[98,1119,685],{"class":204},[98,1121,1122],{"class":100,"line":233},[98,1123,690],{"class":204},[98,1125,1126],{"class":100,"line":244},[98,1127,685],{"class":204},[98,1129,1130],{"class":100,"line":249},[98,1131,1132],{"class":204},"# positional arguments:\n",[98,1134,1135],{"class":100,"line":254},[98,1136,1137],{"class":204},"# title                  the title of the blog post\n",[98,1139,1140],{"class":100,"line":267},[98,1141,685],{"class":204},[98,1143,1144],{"class":100,"line":587},[98,1145,699],{"class":204},[98,1147,1148],{"class":100,"line":592},[98,1149,704],{"class":204},[98,1151,1152],{"class":100,"line":597},[98,1153,1154],{"class":204},"#  -d, --draft           create a draft blog post, defaults to 'True'\n",[98,1156,1157],{"class":100,"line":612},[98,1158,1159],{"class":204},"#  -o, --output OUTPUT   directory to save the Markdown file at, defaults to\n",[98,1161,1162],{"class":100,"line":618},[98,1163,1164],{"class":204},"#                        \"\u002Fhome\u002Fjohndoe\u002Fblogposts\"\n",[10,1166,1167,1168,1170,1171,1174,1175,1177,1178,1180],{},"Again we clearly now have a positional argument (",[31,1169,736],{},"), the optional\n(",[31,1172,1173],{},"-draft",") and ",[31,1176,743],{}," flags thanks to the ",[31,1179,33],{}," module!",[10,1182,1183,1184,1187,1188,1191,1192,1195,1196,1199,1200,1203,1204,1206,1207,1209,1210,1212,1213,1215,1216,1218],{},"In the current version of our code, our ",[31,1185,1186],{},"parse_args()"," function basically\ncreates a \"parser\" from the ",[31,1189,1190],{},"ArgumentParser()"," object and returns a ",[31,1193,1194],{},"Namespace","\nobject containing all the arguments and flags of our application. Internally,\nour ",[31,1197,1198],{},"parser"," object containing the ",[31,1201,1202],{},"add_argument()"," and the ",[31,1205,1186],{},"\nmethods among a few which is not discussed in this article. The ",[31,1208,1202],{},"\nmethod is used to append the CLI arguments and flags to the ",[31,1211,1194],{}," object\nand the ",[31,1214,1186],{}," method creates the ",[31,1217,1194],{}," object.",[10,1220,78,1221,1223,1224,1227,1228,1230,1231,1234,1235,1237,1238,1241,1242,1244],{},[31,1222,1202],{}," method also accepts a bunch of parameters some of which are\nused in our use case. To start with, the ",[31,1225,1226],{},"store_true"," parameter configures our\n",[31,1229,740],{}," flag to instruct the application to false a \"truth-ey value\" if\nexplicitly passed and vice versa. We can also provide some type-casting in the\napplication by passing the ",[31,1232,1233],{},"type"," parameter. For example, our ",[31,1236,743],{}," flag\nonly accepts a filesystem path-like object. In addition, we also set a default\nto the flag by using the ",[31,1239,1240],{},"default"," parameter which is set to a file-path. The\ndefault variable is set using the ",[31,1243,975],{}," module which itself deserves another\narticle of its own so I won't discuss much about it right now.",[10,1246,1247,1248,1250,1251,1253,1254,1256],{},"The list of supported parameters for the ",[31,1249,1202],{}," method is larger and I\nonly briefly glanced over them in my article. Hence I urge you to check out the\nofficial documentation of the ",[31,1252,33],{}," module to learn more about the\nparameters to pass to the ",[31,1255,1202],{}," method.",[10,1258,1259],{},"With the changes mentioned above, we now have a working CLI interface but it is\ndumb and does nothing other than notify the user about its potential\ncapabilities. So in the next section of the document we'll implement the logic\nto handle those functions.",[59,1261,1263],{"id":1262},"reading-the-template-and-generating-the-boilerplate","Reading the Template and Generating the Boilerplate",[10,1265,1266],{},"Our CLI application is only halfway implemented and does nothing other than\nparsing the CLI arguments and exiting the execution loop successfully. You can\nverify it by invoking the following command and checking whether the command\nexits without any issues;",[89,1268,1270],{"className":277,"code":1269,"language":279,"meta":94,"style":94},".\u002Fmkblog \"Hello World\"\n# Outputs nothing\n# Verify command ran successful by checking exit code\n# echo $?\n",[31,1271,1272,1278,1283,1288],{"__ignoreMap":94},[98,1273,1274,1276],{"class":100,"line":101},[98,1275,334],{"class":126},[98,1277,337],{"class":126},[98,1279,1280],{"class":100,"line":123},[98,1281,1282],{"class":204},"# Outputs nothing\n",[98,1284,1285],{"class":100,"line":179},[98,1286,1287],{"class":204},"# Verify command ran successful by checking exit code\n",[98,1289,1290],{"class":100,"line":228},[98,1291,1292],{"class":204},"# echo $?\n",[10,1294,1295],{},"At the beginning of the article I mentioned the purpose of the script and tasks\nit is expected to fulfill but as a reminder here's what it should do:",[70,1297,1298,1301,1304],{},[73,1299,1300],{},"Read and parse some pre-defined template content.",[73,1302,1303],{},"Create a Markdown file and populate it with the pre-defined template content.",[73,1305,1306],{},"Notify the user of the task completion.",[10,1308,1309,1310,1313,1314,1317],{},"So in this section we'll work on implementing the logic to handle those tasks\nproperly. To do so, we'll first define two utility functions -\n",[31,1311,1312],{},"create_content()"," and ",[31,1315,1316],{},"write_file()"," .",[10,1319,1320,1321,1323],{},"Our ",[31,1322,1312],{}," function will be defined as such:",[89,1325,1327],{"className":91,"code":1326,"filename":45,"language":93,"meta":94,"style":94},"# .... truncated code\n\ndef create_content(title: str, draft: bool) -> str:\n        \"\"\"Parse and generate the Markdown content to write to file.\"\"\"\n        status = \"draft\" if draft else \"published\"\n\n        return f\"\"\"\n        ---\n        title: {title}\n        status: {status}\n        ---\n        \"\"\"\n\n# .... truncated code\n",[31,1328,1329,1334,1338,1369,1374,1396,1400,1411,1416,1424,1432,1436,1441,1445],{"__ignoreMap":94},[98,1330,1331],{"class":100,"line":101},[98,1332,1333],{"class":204},"# .... truncated code\n",[98,1335,1336],{"class":100,"line":123},[98,1337,211],{"emptyLinePlaceholder":210},[98,1339,1340,1342,1345,1348,1351,1354,1357,1360,1363,1365,1367],{"class":100,"line":179},[98,1341,156],{"class":104},[98,1343,1344],{"class":126}," create_content",[98,1346,1347],{"class":108},"(title:",[98,1349,1350],{"class":871}," str",[98,1352,1353],{"class":108},",",[98,1355,1356],{"class":108}," draft:",[98,1358,1359],{"class":871}," bool",[98,1361,1362],{"class":108},")",[98,1364,165],{"class":108},[98,1366,1350],{"class":871},[98,1368,120],{"class":108},[98,1370,1371],{"class":100,"line":228},[98,1372,1373],{"class":116},"        \"\"\"Parse and generate the Markdown content to write to file.\"\"\"\n",[98,1375,1376,1379,1381,1384,1387,1390,1393],{"class":100,"line":233},[98,1377,1378],{"class":108},"        status ",[98,1380,556],{"class":112},[98,1382,1383],{"class":116}," \"draft\"",[98,1385,1386],{"class":104}," if",[98,1388,1389],{"class":108}," draft ",[98,1391,1392],{"class":104},"else",[98,1394,1395],{"class":116}," \"published\"\n",[98,1397,1398],{"class":100,"line":244},[98,1399,211],{"emptyLinePlaceholder":210},[98,1401,1402,1405,1408],{"class":100,"line":249},[98,1403,1404],{"class":104},"        return",[98,1406,1407],{"class":871}," f",[98,1409,1410],{"class":116},"\"\"\"\n",[98,1412,1413],{"class":100,"line":254},[98,1414,1415],{"class":116},"        ---\n",[98,1417,1418,1421],{"class":100,"line":267},[98,1419,1420],{"class":116},"        title: ",[98,1422,1423],{"class":108},"{title}\n",[98,1425,1426,1429],{"class":100,"line":587},[98,1427,1428],{"class":116},"        status: ",[98,1430,1431],{"class":108},"{status}\n",[98,1433,1434],{"class":100,"line":592},[98,1435,1415],{"class":116},[98,1437,1438],{"class":100,"line":597},[98,1439,1440],{"class":116},"        \"\"\"\n",[98,1442,1443],{"class":100,"line":612},[98,1444,211],{"emptyLinePlaceholder":210},[98,1446,1447],{"class":100,"line":618},[98,1448,1333],{"class":204},[10,1450,1320,1451,1453,1454,1456,1457,1460],{},[31,1452,1312],{}," function fulfills the simple purpose of returning an\nextrapolated string of YAML front-matter which can be added to our freshly\ngenerated Markdown file. The extrapolated string is created based on the ",[31,1455,736],{},"\nand ",[31,1458,1459],{},"draft"," parameters passed to the function which is the title of the blog\npost and the publication status, respectively.",[10,1462,1463,1464,1469],{},"The function uses \"f-strings\" which is a pretty powerful means of dynamically\nmanipulating strings in Python. This feature of the Python programming language\nis quite powerful and one of my favourite feature of the language, hence I plan\nto write an explicit article on it someday. Until then I strongly urge you to\ncheck out\n",[17,1465,1468],{"href":1466,"rel":1467},"https:\u002F\u002Fdocs.python.org\u002F3\u002Freference\u002Flexical_analysis.html#f-strings",[21],"the relevant docs","\nto learn more).",[10,1471,78,1472,1474],{},[31,1473,1316],{}," function on the other hand is defined as such:",[89,1476,1478],{"className":91,"code":1477,"language":93,"meta":94,"style":94},"import re\n\n# .... truncated code\n\ndef write_file(title: str, output: pathlib.Path, draft: bool) -> pathlib.Path | None:\n        \"\"\"Generate a Markdown file based on the created content.\"\"\"\n        if not pathlib.Path(output)is_dir():\n                pathlib.Path(output).mkdir(parents=True, exists_ok=True)\n\n        title = re.sub(r\"[^a-z0-9-]\", \"\", title.lower().replace(\" \", \"-\"))\n        filepath = output \u002F title \u002F \".md\"\n\n        if filepath.exists():\n                print(f\"[ERROR] {filepath} aleady exists, not overwriting it...\")\n                return None\n\n        content = create_content(title, draft)\n        filepath.write_text(content)\n\n        return filepath\n\n# .... truncated code\n",[31,1479,1480,1487,1491,1495,1499,1545,1550,1573,1606,1610,1681,1701,1705,1719,1740,1748,1752,1764,1777,1781,1788,1792],{"__ignoreMap":94},[98,1481,1482,1484],{"class":100,"line":101},[98,1483,360],{"class":359},[98,1485,1486],{"class":108}," re\n",[98,1488,1489],{"class":100,"line":123},[98,1490,211],{"emptyLinePlaceholder":210},[98,1492,1493],{"class":100,"line":179},[98,1494,1333],{"class":204},[98,1496,1497],{"class":100,"line":228},[98,1498,211],{"emptyLinePlaceholder":210},[98,1500,1501,1503,1506,1508,1510,1512,1515,1517,1519,1521,1523,1525,1527,1529,1531,1533,1535,1538,1541,1543],{"class":100,"line":233},[98,1502,156],{"class":104},[98,1504,1505],{"class":126}," write_file",[98,1507,1347],{"class":108},[98,1509,1350],{"class":871},[98,1511,1353],{"class":108},[98,1513,1514],{"class":108}," output:",[98,1516,823],{"class":126},[98,1518,396],{"class":395},[98,1520,828],{"class":126},[98,1522,1353],{"class":108},[98,1524,1356],{"class":108},[98,1526,1359],{"class":871},[98,1528,1362],{"class":108},[98,1530,165],{"class":108},[98,1532,823],{"class":126},[98,1534,396],{"class":395},[98,1536,1537],{"class":126},"Path ",[98,1539,1540],{"class":112},"|",[98,1542,169],{"class":168},[98,1544,120],{"class":108},[98,1546,1547],{"class":100,"line":244},[98,1548,1549],{"class":116},"        \"\"\"Generate a Markdown file based on the created content.\"\"\"\n",[98,1551,1552,1555,1558,1560,1562,1564,1567,1570],{"class":100,"line":249},[98,1553,1554],{"class":104},"        if",[98,1556,1557],{"class":112}," not",[98,1559,823],{"class":108},[98,1561,396],{"class":395},[98,1563,828],{"class":126},[98,1565,1566],{"class":108},"(output)",[98,1568,1569],{"class":126},"is_dir",[98,1571,1572],{"class":108},"():\n",[98,1574,1575,1578,1580,1582,1584,1586,1589,1592,1594,1597,1600,1602,1604],{"class":100,"line":254},[98,1576,1577],{"class":108},"                pathlib",[98,1579,396],{"class":395},[98,1581,828],{"class":126},[98,1583,1566],{"class":108},[98,1585,396],{"class":395},[98,1587,1588],{"class":126},"mkdir",[98,1590,1591],{"class":108},"(parents",[98,1593,556],{"class":112},[98,1595,1596],{"class":168},"True",[98,1598,1599],{"class":108},", exists_ok",[98,1601,556],{"class":112},[98,1603,1596],{"class":168},[98,1605,191],{"class":108},[98,1607,1608],{"class":100,"line":267},[98,1609,211],{"emptyLinePlaceholder":210},[98,1611,1612,1615,1617,1620,1622,1625,1627,1630,1633,1636,1639,1642,1645,1647,1650,1653,1656,1658,1661,1663,1665,1668,1670,1673,1675,1678],{"class":100,"line":587},[98,1613,1614],{"class":108},"        title ",[98,1616,556],{"class":112},[98,1618,1619],{"class":108}," re",[98,1621,396],{"class":395},[98,1623,1624],{"class":126},"sub",[98,1626,185],{"class":108},[98,1628,1629],{"class":871},"r",[98,1631,1632],{"class":116},"\"",[98,1634,1635],{"class":108},"[",[98,1637,1638],{"class":112},"^",[98,1640,1641],{"class":116},"a-z0-9-",[98,1643,1644],{"class":108},"]",[98,1646,1632],{"class":116},[98,1648,1649],{"class":108},", ",[98,1651,1652],{"class":116},"\"\"",[98,1654,1655],{"class":108},", title",[98,1657,396],{"class":395},[98,1659,1660],{"class":126},"lower",[98,1662,162],{"class":108},[98,1664,396],{"class":395},[98,1666,1667],{"class":126},"replace",[98,1669,185],{"class":108},[98,1671,1672],{"class":116},"\" \"",[98,1674,1649],{"class":108},[98,1676,1677],{"class":116},"\"-\"",[98,1679,1680],{"class":108},"))\n",[98,1682,1683,1686,1688,1691,1693,1696,1698],{"class":100,"line":592},[98,1684,1685],{"class":108},"        filepath ",[98,1687,556],{"class":112},[98,1689,1690],{"class":108}," output ",[98,1692,841],{"class":112},[98,1694,1695],{"class":108}," title ",[98,1697,841],{"class":112},[98,1699,1700],{"class":116}," \".md\"\n",[98,1702,1703],{"class":100,"line":597},[98,1704,211],{"emptyLinePlaceholder":210},[98,1706,1707,1709,1712,1714,1717],{"class":100,"line":612},[98,1708,1554],{"class":104},[98,1710,1711],{"class":108}," filepath",[98,1713,396],{"class":395},[98,1715,1716],{"class":126},"exists",[98,1718,1572],{"class":108},[98,1720,1721,1724,1726,1729,1732,1735,1738],{"class":100,"line":618},[98,1722,1723],{"class":126},"                print",[98,1725,185],{"class":108},[98,1727,1728],{"class":871},"f",[98,1730,1731],{"class":116},"\"[ERROR] ",[98,1733,1734],{"class":108},"{filepath}",[98,1736,1737],{"class":116}," aleady exists, not overwriting it...\"",[98,1739,191],{"class":108},[98,1741,1742,1745],{"class":100,"line":626},[98,1743,1744],{"class":104},"                return",[98,1746,1747],{"class":168}," None\n",[98,1749,1750],{"class":100,"line":631},[98,1751,211],{"emptyLinePlaceholder":210},[98,1753,1754,1757,1759,1761],{"class":100,"line":636},[98,1755,1756],{"class":108},"        content ",[98,1758,556],{"class":112},[98,1760,1344],{"class":126},[98,1762,1763],{"class":108},"(title, draft)\n",[98,1765,1766,1769,1771,1774],{"class":100,"line":649},[98,1767,1768],{"class":108},"        filepath",[98,1770,396],{"class":395},[98,1772,1773],{"class":126},"write_text",[98,1775,1776],{"class":108},"(content)\n",[98,1778,1779],{"class":100,"line":939},[98,1780,211],{"emptyLinePlaceholder":210},[98,1782,1783,1785],{"class":100,"line":951},[98,1784,1404],{"class":104},[98,1786,1787],{"class":108}," filepath\n",[98,1789,1790],{"class":100,"line":959},[98,1791,211],{"emptyLinePlaceholder":210},[98,1793,1794],{"class":100,"line":967},[98,1795,1333],{"class":204},[10,1797,1798,1799,1649,1801,1313,1804,1806,1807,1809],{},"The function accepts the ",[31,1800,736],{},[31,1802,1803],{},"output",[31,1805,1459],{}," parameters which are\nbasically the title of the blog post, the file path to write the Markdown file\nto and its publication status. These parameters are passed down to the\n",[31,1808,1312],{}," function from earlier and based on its returned data, a\nMarkdown file be generated at a specified location.",[10,1811,1812],{},"The function also prevents accidentally overwriting the Markdown if it already\nexists and in addition creates the necessary directories (if any are required)\nto store the Markdown file at. Once the function completes the execution of its\nbody, it returns the file path of the generated Markdown file. This output will\nbe used to notify the user about successful execution for better UI\u002FUX.",[59,1814,1816],{"id":1815},"hooking-the-interface-to-the-logic-handlers","Hooking the Interface to the Logic Handlers",[10,1818,1819],{},"Our script is now nearing completion so let us refactor our application so that\nit can pass the user data from the arguments\u002Foptions to our user-defined\nfunctions for logic handling.",[10,1821,1822,1823,1825],{},"We'll have to make the following changes to the ",[31,1824,145],{}," function of our script\nlike this:",[89,1827,1829],{"className":91,"code":1828,"filename":45,"language":93,"meta":94,"style":94},"def main() -> None:\n        \"\"\"Entrypoint of the script.\"\"\"\n        args = parse_args()\n\n        filepath = write_files(args.title, args.output, args.draft)\n\n        if filepath:\n                print(f\"[INFO] Blog template is generated at {str(filepath)}\")\n",[31,1830,1831,1845,1850,1861,1865,1892,1896,1903],{"__ignoreMap":94},[98,1832,1833,1835,1837,1839,1841,1843],{"class":100,"line":101},[98,1834,156],{"class":104},[98,1836,159],{"class":126},[98,1838,162],{"class":108},[98,1840,165],{"class":108},[98,1842,169],{"class":168},[98,1844,120],{"class":108},[98,1846,1847],{"class":100,"line":123},[98,1848,1849],{"class":116},"        \"\"\"Entrypoint of the script.\"\"\"\n",[98,1851,1852,1855,1857,1859],{"class":100,"line":179},[98,1853,1854],{"class":108},"        args ",[98,1856,556],{"class":112},[98,1858,534],{"class":126},[98,1860,130],{"class":108},[98,1862,1863],{"class":100,"line":228},[98,1864,211],{"emptyLinePlaceholder":210},[98,1866,1867,1869,1871,1874,1877,1879,1882,1884,1887,1889],{"class":100,"line":233},[98,1868,1685],{"class":108},[98,1870,556],{"class":112},[98,1872,1873],{"class":126}," write_files",[98,1875,1876],{"class":108},"(args",[98,1878,396],{"class":395},[98,1880,1881],{"class":108},"title, args",[98,1883,396],{"class":395},[98,1885,1886],{"class":108},"output, args",[98,1888,396],{"class":395},[98,1890,1891],{"class":108},"draft)\n",[98,1893,1894],{"class":100,"line":244},[98,1895,211],{"emptyLinePlaceholder":210},[98,1897,1898,1900],{"class":100,"line":249},[98,1899,1554],{"class":104},[98,1901,1902],{"class":108}," filepath:\n",[98,1904,1905,1907,1909,1911,1914,1917,1919,1922,1924],{"class":100,"line":254},[98,1906,1723],{"class":126},[98,1908,185],{"class":108},[98,1910,1728],{"class":871},[98,1912,1913],{"class":116},"\"[INFO] Blog template is generated at ",[98,1915,1916],{"class":108},"{",[98,1918,872],{"class":871},[98,1920,1921],{"class":108},"(filepath)}",[98,1923,1632],{"class":116},[98,1925,191],{"class":108},[10,1927,1928,1929,1932,1933,1935],{},"These new lines of code basically instructs our application to parse the CLI\narguments and store it in the ",[31,1930,1931],{},"args"," variable which are then passed to the\n",[31,1934,1316],{}," function. This function in turn returns the file path to the file\n(if it was successfully written!) and the path is then printed out to the STDOUT\nfor the user.",[10,1937,1938],{},"Putting the entire code together, the complete script should look like this:",[89,1940,1942],{"className":91,"code":1941,"filename":45,"language":93,"meta":94,"style":94},"#!\u002Fusr\u002Fbin\u002Fenv python3\n\nfrom pathlib import Path\nfrom argparse import ArgumentParser, Namespace\n\n\ndef create_content(title: str, draft: bool) -> str:\n        \"\"\"Parse and generate the Markdown content to write to file.\"\"\"\n        status = \"draft\" if draft else \"published\"\n\n        return f\"\"\"\n        ---\n        title: {title}\n        status: {status}\n        ---\n        \"\"\"\n\n\ndef write_file(title: str, output: Path, draft: bool) -> Path | None:\n        \"\"\"Generate a Markdown file based on the created content.\"\"\"\n        if not Path(output)is_dir():\n                Path(output).mkdir(parents=True, exists_ok=True)\n\n        filepath = output \u002F title \u002F \".md\"\n\n        if filepath.exists():\n                print(f\"[ERROR] {filepath} aleady exists, not overwriting it...\")\n                return None\n\n        content = create_content(title, draft)\n        filepath.write_text(content)\n\n        return filepath\n\n\ndef parse_args() -> Namespace:\n    \"\"\"Parse and return the arguments\u002Foptions of the script.\"\"\"\n    parser = ArgumentParser(description=\"generate a blog template\")\n\n    blog_dir = Path().home() \u002F \"blogposts\"\n\n    parser.add_agument(\"title\", type=str, help=\"the title of the blog post\")\n    parser.add_argument(\n            \"-d\",\n            \"--draft\",\n            action=\"store_true\",\n            help=\"create a draft blogpost, defaults to 'True'\"\n        )\n        parser.add_argument(\n            \"-o\",\n            \"--output\",\n            type=Path,\n            default=blog_dir\n            help=\"create a draft blogpost, defaults to 'True'\"\n        )\n\n    return parser.parse_args()\n\n\ndef main() -> None:\n        \"\"\"Entrypoint of the script.\"\"\"\n        args = parse_args()\n\n        filepath = write_files(args.title, args.output, args.draft)\n\n        if filepath:\n                print(f\"[INFO] Blog template is generated at {str(filepath)}\")\n\n\nif __name__ == \"__main__\":\n    main()\n",[31,1943,1944,1948,1952,1964,1974,1978,1982,2006,2010,2026,2030,2038,2042,2048,2054,2058,2062,2066,2070,2106,2110,2124,2149,2153,2169,2173,2185,2201,2207,2211,2221,2231,2235,2241,2245,2249,2263,2268,2285,2290,2311,2316,2343,2354,2362,2369,2380,2389,2394,2405,2412,2419,2428,2437,2446,2451,2456,2469,2474,2479,2494,2499,2510,2515,2538,2543,2550,2571,2576,2581,2594],{"__ignoreMap":94},[98,1945,1946],{"class":100,"line":101},[98,1947,205],{"class":204},[98,1949,1950],{"class":100,"line":123},[98,1951,211],{"emptyLinePlaceholder":210},[98,1953,1954,1956,1959,1961],{"class":100,"line":179},[98,1955,511],{"class":359},[98,1957,1958],{"class":108}," pathlib ",[98,1960,360],{"class":359},[98,1962,1963],{"class":108}," Path\n",[98,1965,1966,1968,1970,1972],{"class":100,"line":228},[98,1967,511],{"class":359},[98,1969,514],{"class":108},[98,1971,360],{"class":359},[98,1973,519],{"class":108},[98,1975,1976],{"class":100,"line":233},[98,1977,211],{"emptyLinePlaceholder":210},[98,1979,1980],{"class":100,"line":244},[98,1981,211],{"emptyLinePlaceholder":210},[98,1983,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004],{"class":100,"line":249},[98,1985,156],{"class":104},[98,1987,1344],{"class":126},[98,1989,1347],{"class":108},[98,1991,1350],{"class":871},[98,1993,1353],{"class":108},[98,1995,1356],{"class":108},[98,1997,1359],{"class":871},[98,1999,1362],{"class":108},[98,2001,165],{"class":108},[98,2003,1350],{"class":871},[98,2005,120],{"class":108},[98,2007,2008],{"class":100,"line":254},[98,2009,1373],{"class":116},[98,2011,2012,2014,2016,2018,2020,2022,2024],{"class":100,"line":267},[98,2013,1378],{"class":108},[98,2015,556],{"class":112},[98,2017,1383],{"class":116},[98,2019,1386],{"class":104},[98,2021,1389],{"class":108},[98,2023,1392],{"class":104},[98,2025,1395],{"class":116},[98,2027,2028],{"class":100,"line":587},[98,2029,211],{"emptyLinePlaceholder":210},[98,2031,2032,2034,2036],{"class":100,"line":592},[98,2033,1404],{"class":104},[98,2035,1407],{"class":871},[98,2037,1410],{"class":116},[98,2039,2040],{"class":100,"line":597},[98,2041,1415],{"class":116},[98,2043,2044,2046],{"class":100,"line":612},[98,2045,1420],{"class":116},[98,2047,1423],{"class":108},[98,2049,2050,2052],{"class":100,"line":618},[98,2051,1428],{"class":116},[98,2053,1431],{"class":108},[98,2055,2056],{"class":100,"line":626},[98,2057,1415],{"class":116},[98,2059,2060],{"class":100,"line":631},[98,2061,1440],{"class":116},[98,2063,2064],{"class":100,"line":636},[98,2065,211],{"emptyLinePlaceholder":210},[98,2067,2068],{"class":100,"line":649},[98,2069,211],{"emptyLinePlaceholder":210},[98,2071,2072,2074,2076,2078,2080,2082,2084,2087,2089,2091,2093,2095,2097,2100,2102,2104],{"class":100,"line":939},[98,2073,156],{"class":104},[98,2075,1505],{"class":126},[98,2077,1347],{"class":108},[98,2079,1350],{"class":871},[98,2081,1353],{"class":108},[98,2083,1514],{"class":108},[98,2085,2086],{"class":126}," Path",[98,2088,1353],{"class":108},[98,2090,1356],{"class":108},[98,2092,1359],{"class":871},[98,2094,1362],{"class":108},[98,2096,165],{"class":108},[98,2098,2099],{"class":126}," Path ",[98,2101,1540],{"class":112},[98,2103,169],{"class":168},[98,2105,120],{"class":108},[98,2107,2108],{"class":100,"line":951},[98,2109,1549],{"class":116},[98,2111,2112,2114,2116,2118,2120,2122],{"class":100,"line":959},[98,2113,1554],{"class":104},[98,2115,1557],{"class":112},[98,2117,2086],{"class":126},[98,2119,1566],{"class":108},[98,2121,1569],{"class":126},[98,2123,1572],{"class":108},[98,2125,2126,2129,2131,2133,2135,2137,2139,2141,2143,2145,2147],{"class":100,"line":967},[98,2127,2128],{"class":126},"                Path",[98,2130,1566],{"class":108},[98,2132,396],{"class":395},[98,2134,1588],{"class":126},[98,2136,1591],{"class":108},[98,2138,556],{"class":112},[98,2140,1596],{"class":168},[98,2142,1599],{"class":108},[98,2144,556],{"class":112},[98,2146,1596],{"class":168},[98,2148,191],{"class":108},[98,2150,2151],{"class":100,"line":983},[98,2152,211],{"emptyLinePlaceholder":210},[98,2154,2155,2157,2159,2161,2163,2165,2167],{"class":100,"line":994},[98,2156,1685],{"class":108},[98,2158,556],{"class":112},[98,2160,1690],{"class":108},[98,2162,841],{"class":112},[98,2164,1695],{"class":108},[98,2166,841],{"class":112},[98,2168,1700],{"class":116},[98,2170,2171],{"class":100,"line":1003},[98,2172,211],{"emptyLinePlaceholder":210},[98,2174,2175,2177,2179,2181,2183],{"class":100,"line":1008},[98,2176,1554],{"class":104},[98,2178,1711],{"class":108},[98,2180,396],{"class":395},[98,2182,1716],{"class":126},[98,2184,1572],{"class":108},[98,2186,2187,2189,2191,2193,2195,2197,2199],{"class":100,"line":1013},[98,2188,1723],{"class":126},[98,2190,185],{"class":108},[98,2192,1728],{"class":871},[98,2194,1731],{"class":116},[98,2196,1734],{"class":108},[98,2198,1737],{"class":116},[98,2200,191],{"class":108},[98,2202,2203,2205],{"class":100,"line":1026},[98,2204,1744],{"class":104},[98,2206,1747],{"class":168},[98,2208,2209],{"class":100,"line":1031},[98,2210,211],{"emptyLinePlaceholder":210},[98,2212,2213,2215,2217,2219],{"class":100,"line":1036},[98,2214,1756],{"class":108},[98,2216,556],{"class":112},[98,2218,1344],{"class":126},[98,2220,1763],{"class":108},[98,2222,2223,2225,2227,2229],{"class":100,"line":1051},[98,2224,1768],{"class":108},[98,2226,396],{"class":395},[98,2228,1773],{"class":126},[98,2230,1776],{"class":108},[98,2232,2233],{"class":100,"line":1056},[98,2234,211],{"emptyLinePlaceholder":210},[98,2236,2237,2239],{"class":100,"line":1063},[98,2238,1404],{"class":104},[98,2240,1787],{"class":108},[98,2242,2243],{"class":100,"line":1068},[98,2244,211],{"emptyLinePlaceholder":210},[98,2246,2247],{"class":100,"line":1073},[98,2248,211],{"emptyLinePlaceholder":210},[98,2250,2251,2253,2255,2257,2259,2261],{"class":100,"line":1086},[98,2252,156],{"class":104},[98,2254,534],{"class":126},[98,2256,162],{"class":108},[98,2258,165],{"class":108},[98,2260,541],{"class":126},[98,2262,120],{"class":108},[98,2264,2266],{"class":100,"line":2265},37,[98,2267,548],{"class":116},[98,2269,2271,2273,2275,2277,2279,2281,2283],{"class":100,"line":2270},38,[98,2272,553],{"class":108},[98,2274,556],{"class":112},[98,2276,559],{"class":126},[98,2278,562],{"class":108},[98,2280,556],{"class":112},[98,2282,567],{"class":116},[98,2284,191],{"class":108},[98,2286,2288],{"class":100,"line":2287},39,[98,2289,211],{"emptyLinePlaceholder":210},[98,2291,2293,2295,2297,2299,2301,2303,2305,2307,2309],{"class":100,"line":2292},40,[98,2294,818],{"class":108},[98,2296,556],{"class":112},[98,2298,2086],{"class":126},[98,2300,162],{"class":108},[98,2302,396],{"class":395},[98,2304,835],{"class":126},[98,2306,838],{"class":108},[98,2308,841],{"class":112},[98,2310,844],{"class":116},[98,2312,2314],{"class":100,"line":2313},41,[98,2315,211],{"emptyLinePlaceholder":210},[98,2317,2319,2321,2323,2325,2327,2329,2331,2333,2335,2337,2339,2341],{"class":100,"line":2318},42,[98,2320,853],{"class":108},[98,2322,396],{"class":395},[98,2324,858],{"class":126},[98,2326,185],{"class":108},[98,2328,863],{"class":116},[98,2330,866],{"class":108},[98,2332,556],{"class":112},[98,2334,872],{"class":871},[98,2336,875],{"class":108},[98,2338,556],{"class":112},[98,2340,880],{"class":116},[98,2342,191],{"class":108},[98,2344,2346,2348,2350,2352],{"class":100,"line":2345},43,[98,2347,853],{"class":108},[98,2349,396],{"class":395},[98,2351,891],{"class":126},[98,2353,894],{"class":108},[98,2355,2357,2360],{"class":100,"line":2356},44,[98,2358,2359],{"class":116},"            \"-d\"",[98,2361,902],{"class":108},[98,2363,2365,2367],{"class":100,"line":2364},45,[98,2366,907],{"class":116},[98,2368,902],{"class":108},[98,2370,2372,2374,2376,2378],{"class":100,"line":2371},46,[98,2373,914],{"class":108},[98,2375,556],{"class":112},[98,2377,919],{"class":116},[98,2379,902],{"class":108},[98,2381,2383,2385,2387],{"class":100,"line":2382},47,[98,2384,926],{"class":108},[98,2386,556],{"class":112},[98,2388,931],{"class":116},[98,2390,2392],{"class":100,"line":2391},48,[98,2393,936],{"class":108},[98,2395,2397,2399,2401,2403],{"class":100,"line":2396},49,[98,2398,942],{"class":108},[98,2400,396],{"class":395},[98,2402,891],{"class":126},[98,2404,894],{"class":108},[98,2406,2408,2410],{"class":100,"line":2407},50,[98,2409,954],{"class":116},[98,2411,902],{"class":108},[98,2413,2415,2417],{"class":100,"line":2414},51,[98,2416,962],{"class":116},[98,2418,902],{"class":108},[98,2420,2422,2424,2426],{"class":100,"line":2421},52,[98,2423,970],{"class":108},[98,2425,556],{"class":112},[98,2427,980],{"class":108},[98,2429,2431,2433,2435],{"class":100,"line":2430},53,[98,2432,986],{"class":108},[98,2434,556],{"class":112},[98,2436,991],{"class":108},[98,2438,2440,2442,2444],{"class":100,"line":2439},54,[98,2441,926],{"class":108},[98,2443,556],{"class":112},[98,2445,931],{"class":116},[98,2447,2449],{"class":100,"line":2448},55,[98,2450,936],{"class":108},[98,2452,2454],{"class":100,"line":2453},56,[98,2455,211],{"emptyLinePlaceholder":210},[98,2457,2459,2461,2463,2465,2467],{"class":100,"line":2458},57,[98,2460,574],{"class":104},[98,2462,577],{"class":108},[98,2464,396],{"class":395},[98,2466,582],{"class":126},[98,2468,130],{"class":108},[98,2470,2472],{"class":100,"line":2471},58,[98,2473,211],{"emptyLinePlaceholder":210},[98,2475,2477],{"class":100,"line":2476},59,[98,2478,211],{"emptyLinePlaceholder":210},[98,2480,2482,2484,2486,2488,2490,2492],{"class":100,"line":2481},60,[98,2483,156],{"class":104},[98,2485,159],{"class":126},[98,2487,162],{"class":108},[98,2489,165],{"class":108},[98,2491,169],{"class":168},[98,2493,120],{"class":108},[98,2495,2497],{"class":100,"line":2496},61,[98,2498,1849],{"class":116},[98,2500,2502,2504,2506,2508],{"class":100,"line":2501},62,[98,2503,1854],{"class":108},[98,2505,556],{"class":112},[98,2507,534],{"class":126},[98,2509,130],{"class":108},[98,2511,2513],{"class":100,"line":2512},63,[98,2514,211],{"emptyLinePlaceholder":210},[98,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536],{"class":100,"line":2517},64,[98,2519,1685],{"class":108},[98,2521,556],{"class":112},[98,2523,1873],{"class":126},[98,2525,1876],{"class":108},[98,2527,396],{"class":395},[98,2529,1881],{"class":108},[98,2531,396],{"class":395},[98,2533,1886],{"class":108},[98,2535,396],{"class":395},[98,2537,1891],{"class":108},[98,2539,2541],{"class":100,"line":2540},65,[98,2542,211],{"emptyLinePlaceholder":210},[98,2544,2546,2548],{"class":100,"line":2545},66,[98,2547,1554],{"class":104},[98,2549,1902],{"class":108},[98,2551,2553,2555,2557,2559,2561,2563,2565,2567,2569],{"class":100,"line":2552},67,[98,2554,1723],{"class":126},[98,2556,185],{"class":108},[98,2558,1728],{"class":871},[98,2560,1913],{"class":116},[98,2562,1916],{"class":108},[98,2564,872],{"class":871},[98,2566,1921],{"class":108},[98,2568,1632],{"class":116},[98,2570,191],{"class":108},[98,2572,2574],{"class":100,"line":2573},68,[98,2575,211],{"emptyLinePlaceholder":210},[98,2577,2579],{"class":100,"line":2578},69,[98,2580,211],{"emptyLinePlaceholder":210},[98,2582,2584,2586,2588,2590,2592],{"class":100,"line":2583},70,[98,2585,105],{"class":104},[98,2587,109],{"class":108},[98,2589,113],{"class":112},[98,2591,117],{"class":116},[98,2593,120],{"class":108},[98,2595,2597,2599],{"class":100,"line":2596},71,[98,2598,127],{"class":126},[98,2600,130],{"class":108},[10,2602,2603,2604,2607],{},"If now invoke the script, a template Markdown file will be created at this\nlocation - ",[31,2605,2606],{},"\u002Fhome\u002F\u003CUSERNAME>\u002Fblogposts"," (by default);",[89,2609,2611],{"className":277,"code":2610,"language":279,"meta":94,"style":94},".\u002Fmkblog \"Hello World\"\n# Outputs:\n# [INFO]: Blog template is generated at \u002Fhome\u002Fjohndoe\u002Fblogposts\u002Fhello-world.md\n",[31,2612,2613,2619,2624],{"__ignoreMap":94},[98,2614,2615,2617],{"class":100,"line":101},[98,2616,334],{"class":126},[98,2618,337],{"class":126},[98,2620,2621],{"class":100,"line":123},[98,2622,2623],{"class":204},"# Outputs:\n",[98,2625,2626],{"class":100,"line":179},[98,2627,2628],{"class":204},"# [INFO]: Blog template is generated at \u002Fhome\u002Fjohndoe\u002Fblogposts\u002Fhello-world.md\n",[10,2630,2631],{},"The script's behaviour can be further manipulated by passing the optional flags\nto it. For example, the output file path can be changed like this:",[89,2633,2635],{"className":277,"code":2634,"language":279,"meta":94,"style":94},".\u002Fmkblog \"Hello World\" --output \".\u002Fmy-blog\u002Fblogs\"\n# Outputs:\n# [INFO]: Blog template is generated at \u002Fhome\u002Fjohndoe\u002Fmy-blog\u002Fblogs\u002Fhello-world.md\n",[31,2636,2637,2650,2654],{"__ignoreMap":94},[98,2638,2639,2641,2644,2647],{"class":100,"line":101},[98,2640,334],{"class":126},[98,2642,2643],{"class":126}," \"Hello World\"",[98,2645,2646],{"class":116}," --output",[98,2648,2649],{"class":126}," \".\u002Fmy-blog\u002Fblogs\"\n",[98,2651,2652],{"class":100,"line":123},[98,2653,2623],{"class":204},[98,2655,2656],{"class":100,"line":179},[98,2657,2658],{"class":204},"# [INFO]: Blog template is generated at \u002Fhome\u002Fjohndoe\u002Fmy-blog\u002Fblogs\u002Fhello-world.md\n",[59,2660,2662],{"id":2661},"key-takeaways-and-suggestions","Key Takeaways and Suggestions",[10,2664,2665,2666,2668],{},"If it wasn't obvious already, the builtin ",[31,2667,33],{}," module is extremely\nversatile and usable for everyday usage. At my workplace, we've completely\nditched usage of any 3rd-party packages in favour of the standard library\nprovided facilities!",[10,2670,2671],{},"While the article does not fully describe the entire feature set of the module,\nmy intention of sharing a write-up on the topic was to shed some insight into\nusing the Python standard library to build CLI applications. Of course there is\na lot of room to improve the script even further but that is an assignment I\nwill leave for you to work on your own time and effort.",[10,2673,2674],{},"Regardless some ideas I can share to improve the functioning and implementation\nof the tool are:",[70,2676,2677,2684,2687],{},[73,2678,2679,2680,2683],{},"Improved logging statements (both to file and STDOUT) using the ",[31,2681,2682],{},"logger","\nmodule from the Python standard library. Another awesome piece of work from\nthe standard library which requires its own write-up.",[73,2685,2686],{},"Parse and read templates from disk (or a remote location) and extrapolate the\ndata based on user input. The implementation can also be refactored to use\nOOP patterns to store dynamic logic which should also improve the legibility\nand functionality of the implementation.",[73,2688,2689,2690,2693,2694,2697,2698,2700],{},"Utilise the ",[31,2691,2692],{},".add_mutually_exclusive_group()"," method to implement arguments\nand options which cannot be used together. For example, a ",[31,2695,2696],{},"--status"," and the\nexisting ",[31,2699,740],{}," flags which does not make sense to be included together.",[10,2702,2703],{},"Considering the module's feature set and capabilities, how you implement new\nfunctions for an application is only constrained by your creative thinking!",[2705,2706,2707],"style",{},"html pre.shiki code .sRC7j, html code.shiki .sRC7j{--shiki-default:#A7C080}html pre.shiki code .s67c6, html code.shiki .s67c6{--shiki-default:#859289;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sySyC, html code.shiki .sySyC{--shiki-default:#DBBC7F}html pre.shiki code .safYi, html code.shiki .safYi{--shiki-default:#E67E80}html pre.shiki code .sY0Nt, html code.shiki .sY0Nt{--shiki-default:#D3C6AA}html pre.shiki code .sDOmQ, html code.shiki .sDOmQ{--shiki-default:#E69875}html pre.shiki code .sFWo_, html code.shiki .sFWo_{--shiki-default:#83C092}html pre.shiki code .s9lfW, html code.shiki .s9lfW{--shiki-default:#D699B6}html pre.shiki code .s3WQq, html code.shiki .s3WQq{--shiki-default:#859289}html pre.shiki code .sP-ue, html code.shiki .sP-ue{--shiki-default:#7FBBB3}",{"title":94,"searchDepth":123,"depth":123,"links":2709},[2710,2711,2712,2713,2714],{"id":61,"depth":123,"text":62},{"id":453,"depth":123,"text":454},{"id":1262,"depth":123,"text":1263},{"id":1815,"depth":123,"text":1816},{"id":2661,"depth":123,"text":2662},{"url":2716,"alt":2717},"https:\u002F\u002Fik.imagekit.io\u002Fjarmos\u002Fbuilding-cli-apps-with-argparse.png?updatedAt=1763202236560","Building CLI apps with Python standard library modules","A step-by-step guide to creating Python CLI applications with argparse. Parse\narguments, generate files, and automate tasks using only the standard library.\n","md",{"status":1459},"\u002Fblogs\u002Fbuilding-cli-apps-with-argparse-from-the-standard-library","2025-11-12",{"title":5,"description":2718},{"loc":2721},"blogs\u002Fbuilding-cli-apps-with-argparse-from-the-standard-library","ReHI2bLgGB5lQadr5dPAowV5dhr3pjEmqN1bc7PPdxE",1788604449925]