├── README.md ├── images ├── example-01.svg ├── example-02.svg ├── handcrafted-code-map-andy.png ├── handcrafted-screenshot-based-code-map-andy.jpg ├── literate-drawio-vscode-01.gif ├── literate-drawio-vscode-01.png └── renaming.drawio └── plantuml ├── basic-codemap.puml ├── basic-uml.puml ├── example-01.puml ├── example-02.puml ├── html-example.puml ├── numbered-steps.puml └── python-module.puml /README.md: -------------------------------------------------------------------------------- 1 | # Literate Code Maps 2 | 3 | *Diagramming Methodology Specification - version 1.0 - Author: Andy Bulka* 4 | 5 | Literate Code Maps are diagrams which help programmers understand the structure and behaviour of source code. Based on, but more practical than UML. 6 | 7 | Code Map diagrams differ from UML diagrams in that they 8 | focus on real source code fragments and lots of 9 | rich-text formatted story-telling narrative. 10 | They combine class and sequence diagrams into the same 11 | diagram, offering step by step numbering to follow the behaviour of a use case story. 12 | 13 | > Literate Code Maps - are not to be confused with the Code Map feature of Visual Studio Enterprise. Literate Code Maps are a free and open diagramming methodology. Visual Studio Code Maps is an interactive code relationship analysis tool reserved for Microsoft languages and customers. 14 | 15 | ## The 5 laws of literate code mapping 16 | 17 | 1. Boxes represent any scope or namespace - be creative 18 | 1. Show structure and behaviour in the same diagram 19 | 1. Code compartments in boxes contain code fragments and richly formatted narrative 20 | 1. Lines representing function calls between boxes are numbered to tell a story 21 | 1. Cross reference numbers can appear anywhere to associate ideas 22 | 23 | Think of literate code maps like UML class diagrams where the classes are now boxes which can represent more things, and lines represent function calls as well as structure. Boxes contain one or more extra compartments containing real code fragments and rich narrative. 24 | 25 | # Quick Intro 26 | 27 | ## The secret behind being *Literate* 28 | 29 | Perhaps you have heard of the saying: 30 | 31 | > *Homines dum docent discunt* — Men learn while they teach. - [Seneca](https://en.wikipedia.org/wiki/Docendo_discimus) 32 | 33 | Literate Code Mapping is like teaching, mainly just to yourself. The act of drawing boxes with relationships and writing text explanations - to a decent level of literacy and professionalism *will* lead you to understand the code. 34 | 35 | Similarly, if you are not dealing with code but dealing with another domain - the same principle applies. Writing a high quality article for oneself is like a literate code map, it clarifies and solves. Try it: When taking notes whilst learning a new area - instead of scrappy notes and jumping from thought to thought, try consolidating your notes into a coherent, literate blog post that flow logically. In the end you'll have an artifact or even article that you can possibly share. More importantly, you will have understood the problem domain, and by re-reading your literate artifact, you will be able to rapidly refresh your memory at any time. 36 | 37 | If the domain is not diagram worthy, bullet points and outlines are enough. Some domains don't even need any structure like bullet points and plain text is enough - it's a continuum of tools we employ when being literate. 38 | 39 | The important thing is to be `literate`, which is the step by step thinking and exposition of a problem space to a quality level, which solves and explains the problem, for yourself and others. 40 | 41 | So, when analysing a domain or code-base, don't just rely on scrappy notes and jumping from thought to thought (in the case of code, jumping from file to file, trying to keep things in your head). That won't guarantee a solution. Be literate! 42 | 43 | ## Maximize data density 44 | 45 | As [Tuft](https://www.google.com/search?sxsrf=ACYBGNS1EpNz4CfrqW-z9eFY1ZGPhFiGFQ%3A1574306638649&ei=TgPWXbChJ--CrtoPldyooAE&q=tufte+principles&oq=tufte+principles&gs_l=psy-ab.3..35i39l2j0l3.18998.18998..20000...0.4..0.160.160.0j1......0....1..gws-wiz.......0i71.p3-NlWLJeD4&ved=0ahUKEwiwgNufrfrlAhVvgUsFHRUuChQQ4dUDCAs&uact=5) 46 | says: *"Visual representations of data must tell the truth... Above all else show data... Maximize data density"*. 47 | For programmers, data means source code including variables, data structures, sample values and payloads. 48 | 49 | The traditional UML class 50 | 51 | ![basic uml](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.github.com/abulka/lcodemaps/master/plantuml/basic-uml.puml&fmt=svg) 52 | 53 | becomes data rich and full of useful detail 54 | 55 | ![basic code map](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.github.com/abulka/lcodemaps/master/plantuml/basic-codemap.puml&fmt=svg) 56 | 57 | A diagram need only list the code fragments that are required to tell a particular use case **story**. 58 | 59 | ## Numbered steps 60 | 61 | To avoid being lost in abstraction, Literate Code Maps rely on textual narrative and story telling, 62 | as well as numbered steps for the reader to follow. 63 | A diagram should have a starting point and a series of steps to follow, to tell a use case story: 64 | 65 | ![numbered steps code map](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.github.com/abulka/lcodemaps/master/plantuml/numbered-steps.puml&fmt=svg) 66 | 67 | Notice the box "H" which represents a HTML file - you see, boxes can represent any Namespace or Component - not just Classes, as we will see below. 68 | 69 | ## Cross References 70 | 71 | Use of cross reference numbers in the diagram handle the multi-dimensional relationships involved in thinking about and explaining software, and its cross cutting concerns. 72 | 73 | Cross references are red numbers e.g. `22` or `23` with destinations as e.g. `#22`. 74 | 75 | Lines between boxes are reserved for function calls. 76 | 77 | # Boxes represent any namespace 78 | 79 | In Literate Code Maps diagrams, boxes can represent any Namespace/Component/File/Thing - not just Classes. 80 | Even files can be represented as boxes in the diagram. Code Map diagrams boxes can thus represent: 81 | 82 | * classes 83 | * packages 84 | * functions 85 | * namespaces 86 | * files of any type e.g. HTML 87 | * module files of Python/C/Go/etc 88 | * sub-systems 89 | * any architectural or deployment grouping 90 | * etc. 91 | 92 | Code Map diagrams boxes are not tied to 93 | Object Oriented paradigms, and are thus more universal and useful. 94 | 95 | Instead of using square boxes, you might want to use different shapes e.g. https://blog.anoff.io/puml-cheatsheet.pdf shows all the component types you can use. 96 | 97 | It is recommended that boxes have a "stereotype" indicating what kind of box it is. For example for a Python module is stamped with an "M" which stands for module. To be explicit and less mysterious, we can also spell out the word "MODULE" and the path to the file in the diagram box. For HTML files the icon stereotype should be "H" with the word "HTML FILE" and the path to the file etc. 98 | 99 | ## HTML files as Boxes 100 | 101 | HTML files with fragments of HTML markup can be represented - great for showing how code interacts with HTML templating. 102 | 103 | ![html file code map](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.github.com/abulka/lcodemaps/master/plantuml/html-example.puml&fmt=svg) 104 | 105 | Javascript code fragments residing in the HTML file 106 | can be represented - see above diagram. 107 | 108 | ## Files of functions as Boxes (no classes) 109 | 110 | Files containing *just* functions and variables (no classes) can be represented as boxes in a way that makes them "look like" classes. For example, the Python file "utils.py" 111 | 112 | ```python 113 | x = 1 114 | y = 2 115 | def fred(): 116 | pass 117 | ``` 118 | 119 | turns into 120 | 121 | ![Python module as pseudo class](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.github.com/abulka/lcodemaps/master/plantuml/python-module.puml&fmt=svg) 122 | 123 | which looks like a class, even though there is not a class in sight! 124 | 125 | Thus, Python programmers with modules (files) containing 126 | only variables and functions will be able to visualise their 127 | codebase. The online diagramming tool [GitUML](https://www.gituml.com) already supports reverse engineering Python modules into boxes in this way. 128 | This representational idea is also a boon for Javascript programmers who may have lots of variables and functions, and no classes. Together with the ability to visualise HTML files as boxes, web developers now have the ability to model aspects of their projects. Languages that may have files containing only functions include Python, Javascript, Kotlin, Delphi/Object Pascal, Go, C and many others. Regardless of the language and box type, ideally, the traditional data / behaviour separation in boxes should be followed. 129 | 130 | # Examples 131 | 132 | Here are some more examples of literate code maps. 133 | 134 | 136 | 137 | ![code map example 01](https://raw.githubusercontent.com/abulka/lcodemaps/master/images/example-01.svg?sanitize=true) 138 | 139 | ## More Complex Example 140 | 141 | ![code map example 02](https://raw.githubusercontent.com/abulka/lcodemaps/master/images/example-02.svg?sanitize=true) 142 | 143 | 144 | ## Event Flow (Literate Code Map Diagram) 145 | 146 | Here is an example of a Code Map of the event flow of a small application (my [TodoMVC-OO](https://github.com/abulka/todomvc-oo) implementation in Javascript using traditional OO and Controllers instead of fancy Javascript frameworks). Events are reified as coloured objects, each different event gets a different colour. 147 | 148 | > Note: The eventing pattern depicted here is [Publisher-Subscriber](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) where real event objects are 'broadcast' into the ether/event bus/system/whatever - allowing any code in the system to subscribe and respond - the point is, the code emitting the event does not have references to receiver object/methods. This eventing approach is more flexible and powerful than the [Observer](https://en.wikipedia.org/wiki/Observer_pattern) pattern since the Observer pattern requires observers to know about and subscribe to Subject objects, which is not always possible or convenient. More dicussion on the differences can be found [in this article](https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c) and on [Stackoverflow](https://stackoverflow.com/questions/6439512/difference-between-observer-pattern-and-event-driven-approach). 149 | 150 | >I used to be a fan of the traditional Observer pattern but in my later years find the Publisher-Subscriber pattern to be simpler and more powerful - plus Publisher-Subscriber is built into Javascript you simply `document.addEventListener("hello", (event) => { ... })` to listen and `document.dispatchEvent(new CustomEvent(event_name, { detail: {from: from, data: data } }))` to notify all. 151 | 152 | Notice in the following code map diagram the: 153 | - use of **colour** on the dotted lines *(representing events being triggered and received)* 154 | - use of corresponding **colour** on the event 155 | - use of corresponding **colour** of the code fragments 156 | 157 | ![todomvc_events](https://raw.githubusercontent.com/abulka/todomvc-oo/master/docs/images/todomvc-oo-event-flow-gituml-134.svg?sanitize=true) 158 | *(click on diagram for more detail and the ability to zoom)* 159 | 160 | # Building your own Literate Code Maps 161 | 162 | By building a Literate Code Map of a particular use case story in your code base, you *will* end up understanding the source code - and be able to effectively fix a bug or add a feature. As a bonus, you can refer to your code map in the future, and share it with colleagues. 163 | 164 | >Think of building a Literate Code Map as the process of "taking notes" as you study code - something that most programmers aleady do. The benefit of a code map is that the diagram auto-lays out as it grows and changes, always looking good. Plus by following the 5 laws of literate code mapping, you have a simple methodology to follow rather than unstructured and undisciplined personal notes that cannot be shared and will likely end up in the bin. 165 | 166 | It is recommended to use [PlantUML](https://plantuml.com/) markup to generate your Literate Code Map diagrams. [GitUML](https://www.gituml.com) supports building Literate Code Map diagrams online with a built in PlantUML editor plus useful PlantUML snippets that support Literate Code Mapping e.g. adding a "code compartment" or cross reference. 167 | 168 | To create code compartment in boxes/classes, use the following markdown sytax e.g.: 169 | 170 | .. def myfunction(param1, param2): .. 171 | 172 | For each box, first list attributes and methods 173 | in the traditional UML way, with a dividing line between them - let's 174 | call this the summary view. 175 | Then append extra compartments within the box for each method 176 | we are interested in expanding upon with code detail and text narrative. 177 | Any methods which are expanded, should be bolded in the UML summary view 178 | of methods, so that the reader knows there is more detail below. 179 | 180 | > When using PlantUML to build Literate Code Maps, it is useful to know that you can define a class multiple times and PlantUML will combine all the definitions into the one box. In this way you can incrementally build up the diagram. You could create one piece of PlantUML to define the summary view, then another piece of PlantUML to create the additional code detail compartments. 181 | 182 | class Shape { 183 | canvas 184 | parent 185 | -- 186 | SetCanvas() 187 | GetParent() 188 | SetBrush() 189 | } 190 | 191 | class Shape { 192 | .. SetCanvas() .. 193 | Narrative rich text comments and code about this method here. 194 | 195 | .. GetParent() .. 196 | Narrative rich text comments and code about this method here. 197 | $code ... 198 | } 199 | 200 | ## Source Code fragments 201 | To include source code fragments inside the "code compartments" of boxes, define this handy macro at the top of your PlantUML e.g.: 202 | 203 | !$code = "" 204 | 205 | then simply add the word `code` before each line of your code fragment. Use spaces to indent, or `\t`. 206 | 207 | $code for i in range(100): 208 | $code print(i) 209 | 210 | >__Tip:__ By using [GitUML](https://www.gituml.com) to build code maps, you can use simply surround source code fragments with the usual ``` syntax - which is much easier than prepending `$code` before each source code line. 211 | 212 | View the [examples directory](https://github.com/abulka/lcodemaps/tree/master/plantuml) for the full source code to the code maps used in this article. 213 | 214 | ## Tools to build code maps 215 | [GitUML](https://www.gituml.com) supports building UML and Literate Code Map diagrams online, using a combination of source code reverse engineering and PlantUML markup. 216 | 217 | [![](https://img.youtube.com/vi/ZnVMOhaIIM8/0.jpg)](https://www.youtube.com/watch?v=ZnVMOhaIIM8 "Quick Start - Creating a diagram and adding a code map compartment") 218 | *2 minute video tutorial* 219 | 220 | ### Zoom 221 | 222 | Literate Code Maps are ideally generated as `.svg` files so that users can zoom in to view more detail and there is never any loss of important detail. All the diagrams in this article are SVG and can be clicked on and zoomed into. 223 | 224 | ### Diagram Size 225 | 226 | The public PlantUML server used to render some of these code maps has a limit on the size of the diagrams it produces. You can easily set up your own PlantUML server locally which not only is faster, but can generate much larger diagrams before clipping them. See the [PlantUML Server Documentation](https://plantuml.com/server). See also the helpfile in [Pynsource](www.pynsource.com) (Python UML tool for Mac, Windows, Linux) as it contains more specific PlantUML server local install instructions. 227 | 228 | # History - Why Code Maps? 229 | 230 | Whenever I have encountered a huge codebase, or an old project I wrote myself, or just a complex code area - I typically trace out and make notes as I read the code. How many programmers do something similar? Here is an example of a hand-crafted literate code map: 231 | 232 | ## Evolution - stage 1 233 | ![hand_crafted_early_code_map](https://raw.github.com/abulka/lcodemaps/master/images/handcrafted-code-map-andy.png) 234 | 235 | Over the years, this became a useful and effective habit: when stuck, take a breath, respect the difficulty of the situation and - build a literate code map. Eventually I started copying and pasting code fragment screenshots into a paint tool - why re-write when you can copy and paste! I connected the boxes with lines and added use usual narrative text comments. This was a *little* more professional, but gee - I wish I had auto-layout so that I could squeeze and rearrange the diagram when I discovered new things. Here is an example of an old hand built 'digital' literate code map diagram: 236 | 237 | ## Evolution - stage 2 238 | ![screenshot_crafted_early_code_map](https://raw.github.com/abulka/lcodemaps/master/images/handcrafted-screenshot-based-code-map-andy.jpg) 239 | 240 | Finally I moved on to using PlantUML markup code - allowing the diagrams and notes to be automatically laid out, be pretty to look at and maintainable. 241 | 242 | ## Evolution - stage 3 243 | ![code map example 01](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.github.com/abulka/lcodemaps/master/plantuml/example-01.puml&fmt=svg) 244 | 245 | The above diagram was created in [GitUML](https://www.gituml.com) which supports building Literate Code Map diagrams online with a built in PlantUML editor plus useful PlantUML snippets that support Literate Code Mapping e.g. adding a "code compartment" or cross reference. 246 | 247 | This is still "state of the art" in terms of convenience and automatic layout etc, despite my thoughts below on the evolution of Literate Code Mapping stage 4. But if you want a really handcrafted, super-precise diagram with close links to your source code and use the vscode editor, read on! 248 | 249 | ## Evolution - stage 4 (2021) 250 | 251 | In June 2021 I discovered that the free diagramming tool draw.io had an [extension for Vscode](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio&ssr=false#overview), which allowed you to draw diagrams without leaving vscode. So for fun I manually built a literate code map of some code I was working on - all handcrafted with copy and paste etc. By breaking up the diagram into smaller pieces (viz. ensure a function call source line or a method definition source line is its own text object in draw.io), I was able to get arrow 'calls' from and to the actual line of source code that I wanted - very precise. 252 | 253 | ![literate-drawio-vscode-01](images/literate-drawio-vscode-01.png) 254 | *Demonstrates a literate code map built in draw.io* 255 | 256 | In this diagram source code areas (a key feature of literate code mapping) are attempted as both a screen shot (faster to make) and also as copy pasted text (had to correct indenting after paste). 257 | 258 | Draw.io has 259 | - lots of formatting (text, colour etc) choices for my diagramming. 260 | - the ability to collapse and expand diagram areas - very handy when building large diagrams - there is a layout feature too. 261 | - custom template shapes - I'm thinking about building a set to help hand craft literate code maps using draw.io. 262 | - an ability to liveshare to present or edit diagrams collaboratively 263 | - the ability to paste screenshots into a diagram - I used this to screen shot some code rather futzing with copy paste of text and correcting and indenting the resulting diagram text box - it was faster. 264 | - the ability to link a screenshot or a text box node of e.g. a react component "MyComponent" with its source by naming the node "#MyComponent"? 265 | 266 | This last feature (the 'code linking' feature of draw.io) is possibly the coolest - whereby adding `#symbolname` text to any diagram object auto navigates to the source code of that symbol when you double clicked it (make sure drawio code linking is toggled on in the bottom status bar). This means that I can jump from the diagram to the source code with a double click - very powerful. 267 | 268 | ![literate-drawio-vscode-01 - image ref which works on main github page](images/literate-drawio-vscode-01.gif) 269 | *Demonstrates a literate code map built in draw.io, double clicking on diagram areas to jump to associated source code & collapsing diagram areas.* 270 | 271 | - View raw gif [literate-drawio-vscode-01](https://github.com/abulka/lcodemaps/blob/master/images/literate-drawio-vscode-01.gif) gif. 272 | - Download the above [renaming.drawio](https://github.com/abulka/lcodemaps/blob/master/images/renaming.drawio) diagram file. 273 | 274 | 275 | ### Further thoughts on draw.io 276 | Whilst this 'stage 4' evolution of Literate Code Mapping is not as automated and rapid as that supported by [GitUML](https://www.gituml.com), it does show how a rich diagramming tool could take literate code mapping to another level - esp. with with source code linking and collaboration features. 277 | 278 | The draw.io approach to building literate code maps is currently relatively slow and tedious, very hand crafted, very 'literate' in the sense that you have lots more control to be as non-UML as you like. To mitigate this with some automation, I'm thinking that a Literate Code Mapping Vscode extension could perhaps be built to communicate with the draw.io diagram to automatically parse code, build UML style objects and embed literate code mapping areas automatically, from selected code fragments. 279 | 280 | 281 | # Tufte 282 | 283 | Whilst I knew my literate code maps worked (for me, personally) - I didn't have any research or scientific backing for them. 284 | 285 | Then I thought of Edward Tuft's visualisation work, which I have been a fan of for a long time. Sure enough, his ideas on density, detail, rich formatting, use of icons and small graphics at any place where you would put text - are all consistent with literate code maps. Especially the idea of combining detail and abstraction in the same diagram - the boxes are the abstraction and the code is the detail. 286 | 287 | I hope to tie more of Tufte's insights into the Literate Code Map methodology, as well as draw from the vast field of [Information Visualization](https://en.wikipedia.org/wiki/Information_visualization). 288 | 289 | > Wikipedia says “Information visualization presumes that "visual representations and interaction techniques take advantage of the human eye’s broad bandwidth pathway into the mind to allow users to see, explore, and understand large amounts of information at once. Information visualization focused on the creation of approaches for conveying abstract information in intuitive ways. 290 | 291 | I also like many of the ideas of [C4 architecture notation](https://c4model.com) e.g. each line being labelled and having a direction. C4 also has some good criticisms of UML in the main C4 video, which I encourage people to watch. 292 | 293 | # UML 294 | 295 | Literate Code Maps are not UML. 296 | 297 | Code Maps are more detailed and can actually be used to debug code and add features, because they contain code fragments which are relevant to day to day programmers. They contain narrative, story telling text - which leverages how we learn. They also take advantage of the human eye’s broad bandwidth pathway to take in visual information. 298 | 299 | UML *is* useful when judiciously used, but most UML diagrams tend to be too abstract to actually be helpful to a programmer. As I write in [my blog](https://www.andypatterns.com/), 300 | 301 | > UML (Unified Modelling Language) has fallen out of favour in the last decade and now tends to only get used in the most basic of ways. Sketches on whiteboards to commuicate class relationships or code execution sequences. Or simple diagrams in documentation and designs. Nobody uses the complex notations of UML 2, because digramming cannot keep up with the myriad programming techniques and paradigms of 2018 - code is simply not reducible to visual information. 302 | 303 | I think that there are a limited numbers of cases where UML diagrams might be helpful: 304 | - whiteboard communication 305 | - database modelling 306 | - reverse engineering existing projects to understand them (see [GitUML](https://gituml.com)) 307 | - big picture architecture diagrams (also see [C4 architecture notation](https://c4model.com)) 308 | - class diagrams of complex class relationships 309 | - representing design patterns 310 | - sequence diagrams of certain code behaviours 311 | - state diagrams are also sometimes useful 312 | 313 | UML usually lacks normal human, tutorial-like, step by step narrative textual storytelling and code level detail. Staring at at a UML diagram typically leaves you wanting much more detailed information. Staring at source code typically leaves you wanting a diagram of relationships and context. Literate Code Maps are an attempt to solve this problem. 314 | 315 | # Contributing 316 | 317 | If you wish to contribute to this diagramming specification I am open to pull requests via the project's dedicated [Github Repository](https://github.com/abulka/lcodemaps). 318 | 319 | 320 | Let's make Literate Code Mapping a practical, useful visualisation technology that can, on ocassion, help software development - without the old UML preconceptions and baggage. 321 | 322 | # Resources 323 | 324 | - Literate Code Maps [Documentation Home Page](https://abulka.github.io/lcodemaps/). 325 | - Literate Code Maps [Github Repository](https://github.com/abulka/lcodemaps) 326 | -------------------------------------------------------------------------------- /images/example-01.svg: -------------------------------------------------------------------------------- 1 | Prototype of "Literate Code Maps"(c) Andy Bulka 2019ViewonKeyUp()onClick()Responsible for handling the clicl event.CallsGetShapeList()on theDiagramto get all the shapes, then iteratesthrough them, checking each shape's parent...for shape in self.GetDiagram().GetShapeList():2if shape.GetParent()== None:3..defonClick()Handles theESCkey being hit and switches the canvas..shape.SetCanvas(None)4..defonKeyUp(self, event)ShapecanvasparentSetCanvas()GetParent()SetBrush()...self.canvas = canvas4...SetCanvas(canvas)Simply a reference to the.parentproperty,which isNoneif there is no parent....return self.parentGetParent()«lib.ogl._lines.py»DiagramshapesGetShapeList()Render()Clear()Scan()...result = []for each shape in shapes:various filtering conditions, if succesful,shape gets appended to the resultreturn resultGetShapeList()Create fresh visualisationfor node in self.displaymodel.graph.nodes:assert not node.shapeif isinstance(node, CommentNode) or hasattr(node, "comment"):shape = self.createCommentShape(node)22else:shape = self.CreateUmlShape(node)Render()«start here»Scenario1when user clicks on buttonThe creation of a comment shapeif a little different22The list of shapes is kept in the diagram, which the view uses1. onClick()2. GetShapeList()-> List3. GetParent()-> Shape4. SetCanvas()A "Literate Code Map" diagramcombines class and sequence diagrams into one.Plus adds code fragments, rich narrative text and numbered references. -------------------------------------------------------------------------------- /images/example-02.svg: -------------------------------------------------------------------------------- 1 | File Upload Story - Literate Code Map(c) Andy Bulka 2019«singlepage/urls.py»urlsSTART HERE: when user clicks on file upload link/fileupload ->views.fileupload1* main user start point/upload_file ->views.upload_file/file_upload_uml/<str:event>/ ->views.file_upload_umlurlpatterns« singlepage.views.py»views1.return render(request,"fileupload.html", {"object_list": None})2.def fileupload(request):3.Calculates an "event" based on the user name and date, and stores alluploaded files in a directory UPLOAD_DIR/event.form = FileUploadForm(request.POST, request.FILES or None)file = request.FILES['file']event =calc_upload_eventdir(request.user, datetime.datetime.now())dir = os.path.join(UPLOAD_DIR, event)path = os.path.join(dir, file.name)handle_uploaded_file(file, path)A1.return HttpResponse(json.dumps(data), content_type="application/json")Typical AJAX HttpResponse is<class 'dict'>: {'status': 'success','file': 'display_model.py','event': 'admin-1-2019-04-17-12:12:47'}def upload_file(request, uid=None):A1.This should be changed to using memcache rather than the local file system!path = os.path.join('uploaded', file.name) # work out diros.makedirs(os.path.dirname(path)) # and create dirwith open(path, 'wb+') as destination: ... destination.write(chunk) # writes out filedef handle_uploaded_file(file, path=None):4.6.Called when all files have been successfully uploaded."event" gives us a way to refer to those files.Combines custom plantumltext (which initially will be blank) into a UML diagram based onpathsimage_url, generated_plant_uml_text =uploaded_local_paths_to_uml_url(paths, custom_plant_uml_text, ..., options_uml)then where do we go?well, we render the "fileupload_uml.html" templateform = MyFileUploadForm(initial={"custom_plant_uml_text": ""})which isclass MyFileUploadForm(forms.Form):custom_plant_uml_text = forms.CharField(label="Additional custom plantuml text", required=False, widget=forms.Textarea)Note that it is not inheriting from forms.ModelFormreturn render(request,"fileupload_uml.html",5.{"python_text": python_text,# "show_python_text": True,"generated_plant_uml_text": generated_plant_uml_text,"custom_plant_uml_text": None,"image_url": image_url,"form": form,"event": event,"paths_ro": paths_ro,},def file_upload_uml(request, event):«singlepage/templates/fileupload.html»fileuploadChange /upload-target to your upload address, in this case it is "upload_file"for each file3.<form action="{% url 'upload_file' %}" class="dropzone" id="myAwesomeDropzone">{% csrf_token %}</form>once all files have been uploadedfunction submit_upload_uml_event()event = last_upload_eventwindow.location.href = 'file_upload_uml/[object Event]'4.javascript«singlepage/templates/fileupload_uml.html»fileupload_umlActually displays the UML diagram. The only thing we uploadvia the form is the custom plantuml. The "event" parameter of the urllets us find the source files etc.<form action={% url 'file_upload_uml' event %} method="POST">upload_file() is called multiple times, once for each filepassing back (in the AJAX response) the first calculated eventto the javascript so that all subsequent files get put into thesame event/directoryviews.fileupload_uml()4.is called automaticallyafter the initial file upload, to render theinitial UML.views.fileupload_uml()6.is called by the userpressing "Generate UML" button on subsequentrenderings, as the UML is refined, by fiddlingwith custom plantuml text.(the user cannot edit info/classes etc inthe uploaded Python files themselves)1.fileupload()2.fileuploadtemplate render3.upload_file(data)once for each file via AJAX4.file_upload_uml/[object Event]once all have been uploaded5.fileupload_uml(image_url, form, etc.)template render UML6.file_upload_uml/[object Event]to regenerate the UML -------------------------------------------------------------------------------- /images/handcrafted-code-map-andy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abulka/lcodemaps/d40f7437cbe5a3484fb2136118f2c32a87aa7ceb/images/handcrafted-code-map-andy.png -------------------------------------------------------------------------------- /images/handcrafted-screenshot-based-code-map-andy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abulka/lcodemaps/d40f7437cbe5a3484fb2136118f2c32a87aa7ceb/images/handcrafted-screenshot-based-code-map-andy.jpg -------------------------------------------------------------------------------- /images/literate-drawio-vscode-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abulka/lcodemaps/d40f7437cbe5a3484fb2136118f2c32a87aa7ceb/images/literate-drawio-vscode-01.gif -------------------------------------------------------------------------------- /images/literate-drawio-vscode-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abulka/lcodemaps/d40f7437cbe5a3484fb2136118f2c32a87aa7ceb/images/literate-drawio-vscode-01.png -------------------------------------------------------------------------------- /images/renaming.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /plantuml/basic-codemap.puml: -------------------------------------------------------------------------------- 1 | @startuml Basic idea of a literate code map 2 | 3 | !$code = "" 4 | !$codeb = "" 5 | !$codeg = "" 6 | !$codeb = "" 7 | !$codep = "" 8 | 9 | class Payroll { 10 | last_run: date 11 | employees[]: list #22 $codeg this can be implemented as attr or DB call 12 | ---- 13 | pay() 14 | count() 15 | auth_to_bank() 16 | .. def pay(): .. 17 | We loop through all employees and only pay them 18 | if they can be located. 😄 19 | 20 | $codeb for employee in employees: 21 | $codeb found = employee.locate() 22 | $codeb ... 23 | $codeb ... 24 | $codeb if found: 25 | $codeb bank.send_money( 26 | $codeb \t employee.bank_ac_number, 27 | $codeb \t employee.salary) 28 | $codeb ... 29 | 30 | .. def count(): .. 31 | We simply return the length of the employees 32 | attribute which is a list. 33 | 34 | $codeb return employee.length() see 22. 35 | 36 | However when this is turned into a database facing method in version two 37 | we will need to change the implementation 38 | 39 | 40 | | Version | Implementation |\n| V1 | List |\n| V2 | Django ORM DB query | 41 | 42 | e.g. something like 43 | 44 | $codeb return employee.Objects.all().count() 45 | } 46 | 47 | 48 | @enduml 49 | -------------------------------------------------------------------------------- /plantuml/basic-uml.puml: -------------------------------------------------------------------------------- 1 | @startuml Basic UML class diagram 2 | 3 | class Payroll { 4 | employees[] 5 | pay() 6 | count() 7 | } 8 | 9 | @enduml -------------------------------------------------------------------------------- /plantuml/example-01.puml: -------------------------------------------------------------------------------- 1 | @startuml More complete example of a Literate Code Map - 01 2 | 3 | !$code = "" 4 | !$codeb = "" 5 | !$codeg = "" 6 | !$codeb = "" 7 | !$codep = "" 8 | 9 | header 10 | Prototype of "Literate Code Maps" 11 | (c) Andy Bulka 2019 12 | 13 | endheader 14 | 15 | class View { 16 | onKeyUp() 17 | onClick() 18 | --- 19 | .. def onClick() .. 20 | 21 | Responsible for handling the clicl event. 22 | Calls GetShapeList() on the Diagram to get all the shapes, then iterates 23 | through them, checking each shape's parent. 24 | 25 | $code .. 26 | $code for shape in self.GetDiagram().GetShapeList(): 2 27 | $code if shape.GetParent() == None: 3 28 | $code .. 29 | 30 | .. def onKeyUp(self, event) .. 31 | 32 | Handles the ESC key being hit and switches the canvas 33 | $code .. 34 | $code shape.SetCanvas(None) 4 35 | $code .. 36 | } 37 | class Shape { 38 | canvas 39 | parent 40 | -- 41 | SetCanvas() 42 | GetParent() 43 | SetBrush() 44 | } 45 | class Diagram <> { 46 | shapes 47 | --- 48 | GetShapeList() 49 | Render() 50 | Clear() 51 | Scan() 52 | .. GetShapeList() .. 53 | $code ... 54 | $code result = [] 55 | $code for each shape in shapes: 56 | \tvarious filtering conditions, if succesful, 57 | \tshape gets appended to the result 58 | $code return result 59 | 60 | .. Render() .. 61 | Create fresh visualisation 62 | 63 | $code for node in self.displaymodel.graph.nodes: 64 | $code assert not node.shape 65 | $code if isinstance(node, CommentNode) or hasattr(node, "comment"): 66 | $code shape = self.createCommentShape(node) 22 67 | $code else: 68 | $code shape = self.CreateUmlShape(node) 69 | } 70 | 71 | class Shape { 72 | .. SetCanvas(canvas) .. 73 | $code ... 74 | $code self.canvas = canvas 4 75 | $code ... 76 | .. GetParent() .. 77 | Simply a reference to the .parent property, 78 | which is None if there is no parent. 79 | $code ... 80 | $code return self.parent 81 | } 82 | 83 | class Scenario1 < when user clicks on button ><< (S,#FF7700) start here >> 84 | 85 | Scenario1 ..> View : 1. onClick() 86 | View ..> Diagram : 2. GetShapeList()\n -> List 87 | View ..> Shape : 3. GetParent()\n -> Shape 88 | View ..> Shape : 4. SetCanvas() 89 | 90 | 91 | 92 | note as N2 93 | The creation of a comment shape 94 | if a little different 22 95 | end note 96 | N2 . View 97 | N2 .[hidden] View 98 | 99 | note "The list of shapes is kept in the diagram, which the view uses" as N3 100 | N3 .. View 101 | N3 .. Diagram 102 | 103 | center footer 104 | 105 | A "Literate Code Map" diagram 106 | combines class and sequence diagrams into one. 107 | Plus adds code fragments, rich narrative text and numbered references. 108 | end footer 109 | 110 | @enduml 111 | -------------------------------------------------------------------------------- /plantuml/example-02.puml: -------------------------------------------------------------------------------- 1 | @startuml More complete example of a Literate Code Map - 02 2 | 3 | !$code = "" 4 | !$codeb = "" 5 | !$codeg = "" 6 | !$codeb = "" 7 | !$codep = "" 8 | 9 | header 10 | File Upload Story - Literate Code Map 11 | (c) Andy Bulka 2019 12 | endheader 13 | 14 | class urls < START HERE: when user clicks on file upload link ><<(M,#FF7700) singlepage/urls.py>>{ 15 | .. urlpatterns .. 16 | /fileupload -> $codeb views.fileupload 1 * main user start point 17 | 18 | /upload_file -> $codeb views.upload_file 19 | /file_upload_uml// -> $codeb views.file_upload_uml 20 | } 21 | 22 | class views <<(M,#FF7700) singlepage.views.py>> { 23 | .. def fileupload(request): .. 24 | $codeb 1. 25 | $codeb return render(request, 26 | $codeb\t"fileupload.html", {"object_list": None}) $codeb 2. 27 | 28 | .. def upload_file(request, uid=None): .. 29 | $codeb 3. 30 | Calculates an "event" based on the user name and date, and stores all 31 | uploaded files in a directory UPLOAD_DIR/event. 32 | 33 | $codeb form = FileUploadForm(request.POST, request.FILES or None) 34 | $codeb file = request.FILES['file'] 35 | $codeb event = calc_upload_eventdir(request.user, datetime.datetime.now()) 36 | $codeb dir = os.path.join(UPLOAD_DIR, event) 37 | $codeb path = os.path.join(dir, file.name) 38 | $codeb handle_uploaded_file(file, path) $codeb A1. 39 | 40 | $codeb return HttpResponse(json.dumps(data), content_type="application/json") 41 | Typical AJAX HttpResponse is 42 | $codeg : {'status': 'success', 43 | $codeg 'file': 'display_model.py', 44 | $codeg 'event': 'admin-1-2019-04-17-12:12:47'} 45 | 46 | .. def handle_uploaded_file(file, path=None): .. 47 | $codeb A1. 48 | This should be changed to using memcache rather than the local file system! 49 | $codeb path = os.path.join('uploaded', file.name) # work out dir 50 | $codeb os.makedirs(os.path.dirname(path)) # and create dir 51 | $codeb with open(path, 'wb+') as destination: ... destination.write(chunk) # writes out file 52 | 53 | .. def file_upload_uml(request, event): .. 54 | $codeb 4. 55 | $codeb 6. 56 | Called when all files have been successfully uploaded. 57 | "event" gives us a way to refer to those files. 58 | Combines custom plantumltext (which initially will be blank) into a UML diagram based on paths 59 | 60 | $codeb image_url, generated_plant_uml_text = uploaded_local_paths_to_uml_url( 61 | $codeb paths, custom_plant_uml_text, ..., options_uml 62 | $codeb ) 63 | 64 | then where do we go? 65 | well, we render the "fileupload_uml.html" template 66 | 67 | $codeb form = MyFileUploadForm(initial={"custom_plant_uml_text": ""}) 68 | 69 | which is 70 | $codeb class MyFileUploadForm(forms.Form): 71 | $codeb custom_plant_uml_text = forms.CharField( 72 | $codeb label="Additional custom plantuml text", required=False, widget=forms.Textarea 73 | $codeb ) 74 | Note that it is not inheriting from forms.ModelForm 75 | 76 | $codeb return render( 77 | $codeb request, 78 | $codeb "fileupload_uml.html", $codeb 5. 79 | $codeb { 80 | $codeb "python_text": python_text, 81 | $codeb # "show_python_text": True, 82 | $codeb "generated_plant_uml_text": generated_plant_uml_text, 83 | $codeb "custom_plant_uml_text": None, 84 | $codeb "image_url": image_url, 85 | $codeb "form": form, 86 | $codeb "event": event, 87 | $codeb "paths_ro": paths_ro, 88 | $codeb }, 89 | $codeb 90 | } 91 | 92 | class fileupload <<(T,#FF7700) singlepage/templates/fileupload.html>> { 93 | Change /upload-target to your upload address, in this case it is "upload_file" 94 | for each file 95 | 96 | $codeb 3. 97 | $codeb
98 | $codeb {% csrf_token %} 99 | $codeb
100 | .. javascript .. 101 | once all files have been uploaded 102 | $codeb function submit_upload_uml_event() 103 | $codeb event = last_upload_event 104 | $codeb window.location.href = 'file_upload_uml/[object Event]' $codeb 4. 105 | } 106 | 107 | class fileupload_uml <<(T,#FF7700) singlepage/templates/fileupload_uml.html>> { 108 | Actually displays the UML diagram. The only thing we upload 109 | via the form is the custom plantuml. The "event" parameter of the url 110 | lets us find the source files etc. 111 | $codeb
112 | } 113 | 114 | urls --> views : $codeb 1. fileupload() 115 | views --> fileupload : $codeb 2. fileupload\n template render 116 | fileupload --> views : $codeb 3. upload_file(data)\nonce for each file via AJAX 117 | fileupload --> views : $codeb 4. file_upload_uml/[object Event]\nonce all have been uploaded 118 | views -> fileupload_uml : $codeb 5. fileupload_uml(image_url, form, etc.)\n template render UML 119 | fileupload_uml -> views : $codeb 6. file_upload_uml/[object Event] $codeb \nto regenerate the UML 120 | 121 | note as N1 122 | upload_file() is called multiple times, once for each file 123 | passing back (in the AJAX response) the first calculated event 124 | to the javascript so that all subsequent files get put into the 125 | same event/directory 126 | end note 127 | 128 | N1 .. views 129 | N1 ..[hidden] views 130 | 131 | note as N2 132 | views.fileupload_uml() $codeb 4. is called automatically 133 | after the initial file upload, to render the 134 | initial UML. 135 | 136 | views.fileupload_uml() $codeb 6. is called by the user 137 | pressing "Generate UML" button on subsequent 138 | renderings, as the UML is refined, by fiddling 139 | with custom plantuml text. 140 | 141 | (the user cannot edit info/classes etc in 142 | the uploaded Python files themselves) 143 | end note 144 | 145 | N2 .. fileupload_uml 146 | N2 .. views 147 | 148 | @enduml 149 | -------------------------------------------------------------------------------- /plantuml/html-example.puml: -------------------------------------------------------------------------------- 1 | @startuml example of HTML file in a code map diagram 2 | 3 | !$code = "" 4 | !$codeb = "" 5 | !$codeg = "" 6 | !$codeb = "" 7 | !$codep = "" 8 | !$normal = "" 9 | 10 | class fileupload <<(T,#FF7700) HTML FILE singlepage/templates/fileupload.html>> { 11 | myAwesomeDropzone : form 12 | myCanvas : canvas 13 | --javascript-- 14 | submit_upload_uml_event() 15 | render() 16 | --- 17 | This is the HTML form 18 | 19 | $codeb action="{% url 'upload_file' %}" class="dropzone" id="myAwesomeDropzone"> 20 | $codeb {% csrf_token %} 21 | $codeb 22 | 23 | This is the HTML canvas 24 | 25 | $codeb id="myCanvas" width="200" height="100"> #33 26 | $codeb 27 | 28 | .. function submit_upload_uml_event() .. 29 | once all files have been uploaded we redirect 30 | to the $codeb file_upload_uml/ endpoint 31 | 32 | $codeb ... 33 | $codeb function submit_upload_uml_event() 34 | $codeb event = last_upload_event 35 | $codeb window.location.href = 'file_upload_uml/${event}' 36 | 37 | .. function render(): .. 38 | This uses the canvas see 33 39 | to draw the shape 40 | $codeb ... 41 | $codeb var c = document.getElementById("myCanvas"); $codeg non jquery ref to canvas $normal see 33 42 | $codeb var ctx = c.getContext("2d"); 43 | $codeb ctx.moveTo(0, 0); 44 | $codeb ctx.lineTo(200, 100); 45 | $codeb ctx.stroke(); 46 | $codeb ... 47 | 48 | See [[https://www.w3schools.com/html/html5_canvas.asp]] 49 | for more information on canvas drawing. 50 | } 51 | 52 | @enduml -------------------------------------------------------------------------------- /plantuml/numbered-steps.puml: -------------------------------------------------------------------------------- 1 | @startuml numbered steps 2 | 3 | !$code = "" 4 | !$codeb = "" 5 | !$codeg = "" 6 | !$codeb = "" 7 | !$codep = "" 8 | 9 | class Scenario1 < when user clicks on button ><< (S,#FF7700) start here >> 10 | 11 | class View { 12 | onClick() 13 | } 14 | 15 | class CoordinateManager { 16 | getEventLocation(point) 17 | } 18 | 19 | class Shape { 20 | render() 21 | .. def render(): .. 22 | This uses the canvas to draw the shape 23 | $codeb ... 24 | $codeb var c = document.getElementById("myCanvas"); 25 | $codeb ... 26 | } 27 | 28 | class index <<(H,#FF7700) HTML FILE templates/index.html>> { 29 | This is the HTML fragment containing 30 | the canvas DOM element 31 | 32 | $codeb id="myCanvas"
width="200" height="100"> 33 | $codeb 34 | } 35 | 36 | Scenario1 ..> View : 1. user clicks mouse on canvas\nonClick() 37 | View ..> CoordinateManager : call from onClick()\n2. calculate what is being clicked on\ngetEventLocation(point) 38 | View ..> Shape : call from onClick()\n3. draw the shape\nrender() 39 | Shape ..> index : from render()\n4. refer to the canvas\nid="myCanvas" 40 | 41 | @enduml -------------------------------------------------------------------------------- /plantuml/python-module.puml: -------------------------------------------------------------------------------- 1 | @startuml Example of Python module as pseudo class 2 | 3 | class utils <<(M,#FF7700) MODULE utils.py>> { 4 | x 5 | y 6 | --- 7 | fred() 8 | } 9 | 10 | @enduml 11 | --------------------------------------------------------------------------------