{"id":4202,"date":"2018-09-01T16:52:34","date_gmt":"2018-09-01T14:52:34","guid":{"rendered":"https:\/\/blog.mi.hdm-stuttgart.de\/?p=4202"},"modified":"2023-06-09T12:01:37","modified_gmt":"2023-06-09T10:01:37","slug":"parsing-all-open-source-elm-code","status":"publish","type":"post","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/","title":{"rendered":"Parsing all Open Source Elm Code"},"content":{"rendered":"<p>This project was originally inspired by a <a href=\"https:\/\/www.youtube.com\/watch?v=DsA3MNlz6BU\">talk<\/a> Felipe Hoffa gave at the Github Universe conference last year. He talked about how we can analyse the code hosted on Github at a large scale to learn interesting things. I&#8217;m always excited about learning new programming languages, at the moment my favourite new langue is <a href=\"http:\/\/elm-lang.org\/\">Elm<\/a>, a small functional programming language for building web applications. After watching the talk I thought it would be nice to do this kind of analysis on all the public Elm code hosted on Github.<br \/>\n<!--more--><\/p>\n<h2>Initial Idea<\/h2>\n<p>The big question question is: Why isn&#8217;t it already possible to learn interesting things about the code hosted on Github? The problem is that Github works on the text level. All the code is just a huge collection of text files. It would be much more useful if we could operate on the structure which the text represents. Instead of searching for all files which contain the string &#8220;List.map&#8221; we could precisely search for all source files which actually contain a reference to the List.map function.<\/p>\n<p><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22.png\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"4203\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/screenshot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514-17-22\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22.png\" data-orig-size=\"1524,558\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22-1024x375.png\" class=\"wp-image-4203 aligncenter\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22-300x110.png\" alt=\"\" width=\"573\" height=\"210\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22-300x110.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22-768x281.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22-1024x375.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at14-44696b75-bdc4-4bf4-821d-8a224bab7514.17.22.png 1524w\" sizes=\"auto, (max-width: 573px) 100vw, 573px\" \/><\/a><\/p>\n<p>If we want to scale this approach to all the Elm files hosted on Github we need a few steps:<\/p>\n<ol>\n<li>Find all Elm repos<\/li>\n<li>Parse all the Elm files in each repos and extract the references<\/li>\n<li>Store the references and files in the db so they can be querried later<\/li>\n<\/ol>\n<p>In my implementation I&#8217;ve limited myself to just storing which symbols a file defines and which symbols it references instead of storing the whole syntax tree of each file in the db. The resulting graph structure is represented in the graph below.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"4204\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/screenshot2018-08-31at17-34c42112-185d-4793-9ffc-545d210fcea3-58-58\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at17-34c42112-185d-4793-9ffc-545d210fcea3.58.58.png\" data-orig-size=\"682,996\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ScreenShot2018-08-31at17-34c42112-185d-4793-9ffc-545d210fcea3.58.58\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at17-34c42112-185d-4793-9ffc-545d210fcea3.58.58.png\" class=\" wp-image-4204 aligncenter\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at17-34c42112-185d-4793-9ffc-545d210fcea3.58.58-205x300.png\" alt=\"\" width=\"238\" height=\"348\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at17-34c42112-185d-4793-9ffc-545d210fcea3.58.58-205x300.png 205w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at17-34c42112-185d-4793-9ffc-545d210fcea3.58.58.png 682w\" sizes=\"auto, (max-width: 238px) 100vw, 238px\" \/>I&#8217;m a notorious procrastinater (perfectly proven by the fact that I&#8217;m writting this blogpost on the day of the deadline), therefore I decided to submit my idea as a talk proposal for <a href=\"https:\/\/2018.elmeurope.org\/\">Elm Europe 2018<\/a> to give myself some accountability. I guess you could call this approach &#8220;talk driven development&#8221;. I was lucky and my talk got accepted by the conference.<\/p>\n<h2>Prototype for the&nbsp;conference talk<\/h2>\n<p>The focus of the prototype was to quickly get some results and see for which use-cases such a graph could be useful. I wrote a Node.js script which I ran on my local machine to import the data into a Neo4j Graph Database.<\/p>\n<p>Example of the resulting dependency graph of the <a href=\"https:\/\/github.com\/evancz\/elm-todomvc\">elm-todomvc<\/a> repo:<\/p>\n<p><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17.png\"><br \/>\n<img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"4205\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/screenshot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13-25-17\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17.png\" data-orig-size=\"1000,480\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17.png\" class=\"wp-image-4205 aligncenter\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17-300x144.png\" alt=\"\" width=\"509\" height=\"244\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17-300x144.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17-768x369.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-08-31at18-f0c4c380-94a3-4a49-a2b8-cfe6db859a13.25.17.png 1000w\" sizes=\"auto, (max-width: 509px) 100vw, 509px\" \/><\/a><\/p>\n<p>In my <a href=\"https:\/\/www.youtube.com\/watch?v=HLeQ8OmdJRk\">talk<\/a>, I&#8217;ve demonstrated some examples how you can use the graph<\/p>\n<ul>\n<li>get code examples for any library function<\/li>\n<li>check if a project is referencing any unsafe functions that can lead to runtime exceptions<\/li>\n<\/ul>\n<p>The feedback was very positive, but the most commonly requested feature was to have a simple search interface for the graph that allows you to find code examples from other people to help you understand how to use a specific function.<\/p>\n<h2>Elm function search<\/h2>\n<p>I decided to take the lessons I&#8217;ve learned from my talk and turn the prototype into a practical application which allows people to search for code examples. I had to solve 2 problems to get there:<\/p>\n<ul>\n<li>Move the node.js script into the cloud<\/li>\n<li>Improve the robustness of the parser<\/li>\n<\/ul>\n<h2>Architecture<\/h2>\n<p>I build the crawler based on the AWS lambda architecture. The two steps fetching the repos and parsing each repo are two separate functions. The fetch repos function pushes a message for each new repo it finds to a worker queue which works as a buffer for the parsing jobs. The parse repo function is triggered for each repo in the queue. After the repo is parsed the function writes all the references it found to the database.<\/p>\n<p><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930.png\"><br \/>\n<img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"4206\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930.png\" data-orig-size=\"1792,1063\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"cloudcraft(1)-04c1f6f0-c09a-45ee-998f-f6af1778f930\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930-1024x607.png\" class=\"wp-image-4206 aligncenter\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930-300x178.png\" alt=\"\" width=\"463\" height=\"275\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930-300x178.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930-768x456.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930-1024x607.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/cloudcraft1-04c1f6f0-c09a-45ee-998f-f6af1778f930.png 1792w\" sizes=\"auto, (max-width: 463px) 100vw, 463px\" \/>I&#8217;ve decided to switch to using Postgres instead of Neo4j. Because we&#8217;re just interested in finding matches code examples we don&#8217;t need the graph. Instead we can just store all the references in a simple table with one entry for each reference.<\/a><\/p>\n<pre class=\"prettyprint lang-javascript\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">knex.schema.createTable('references', (table) =&gt; {\n    table.increments('id').notNullable()\n    table.string('package').notNullable()\n    table.string('module').notNullable()\n    table.string('symbol').notNullable()\n    table.string('version').notNullable()\n    table.string('file').notNullable()\n    table.integer('start_col').notNullable()\n    table.integer('start_line').notNullable()\n    table.integer('end_col').notNullable()\n    table.integer('end_line').notNullable()\n    table.string('repo_owner').notNullable()\n    table.string('repo_name').notNullable()\n    table.string('commit_hash').notNullable()\n    table.foreign(['repo_owner', 'repo_name'])\n            .references(['repos.owner', 'repos.name'])\n  })<\/pre>\n<p>The API server can then be implemented as a simple node services which acts as a thin layer between the frontend and the database. I&#8217;ve decided to use zeit.co to host the API server. Zeit is a startup that allows you to deploy Node.js applications to the cloud without any configuration. You can also scale up applications easily by running multiple instances of it which are automatically load balanced.<\/p>\n<h2>Fetching all repos<\/h2>\n<p>I had to go through several iteration to solve this problem because unfortunately Github doesn&#8217;t provide a direct API to get all repos of a specific programming language. Initially I used the <a href=\"https:\/\/cloud.google.com\/bigquery\/public-data\/github\">Github Dataset<\/a> which Filipe also used in his talk. The problem with that approach was that the dataset only contained repos where Github could detect an open source license. This meant that a lot of Elm repos where missing in the data set.<\/p>\n<p>The Elm language itself also has a package repository which contains all the published Elm packages. But this again is just a fraction of all the public elm repos.<\/p>\n<p>Finally I had another look at the Github search API which allows to use programming language as a filter criterion but with the caveat that you can only access the first 1000 results. But I figured out a trick to get around this restriction. I sort the repository by the timestamp when they were last updated then I fetch the first 1000 results. In the next batch I use the timestamp of the last result as an additional filter criterion to get only repos which haven&#8217;t been updated more recently. That way I can incrementally crawl all repos. It&#8217;s a little bit hacky, but it works.<\/p>\n<h2>Sending repos to worker queue<\/h2>\n<p>I&#8217;m using the serverless framework to setup my application. To connect the fetch repos function with the parse repo function I need to define a queue as an additional resource in my serverless.yml<\/p>\n<pre class=\"prettyprint lang-yaml\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">RepoQueue:\n    Type: \"AWS::SQS::Queue\"\n    Properties:\n      QueueName: \"RepoQueue\"<\/pre>\n<p>I also need to add the ACCOUNT_ID and the REPO_QUEUE_NAME to the environment of my lambda functions so I can address the queue I&#8217;ve defined previously<\/p>\n<pre class=\"prettyprint lang-yaml\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">REPO_QUEUE_NAME:\n    Fn::GetAtt:\n      - RepoQueue\n    - QueueName\nACCOUNT_ID:\n    - Ref: 'AWS::AccountId'<\/pre>\n<p>Before our lambda function can send messages I also need to add a new permission in the role statements.<\/p>\n<pre class=\"prettyprint lang-yaml\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">iamRoleStatements:\n    - Effect: Allow\n      Action:\n        - sqs:SendMessage\n      Resource: arn:aws:sqs:*:*:*<\/pre>\n<p>After I&#8217;ve setup the queue and added the correct permissions I can use the AWS SDK to send the fetched repos to the queue.<\/p>\n<pre class=\"prettyprint lang-javascript\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">const AWS = require('aws-sdk')\nconst {REPO_QUEUE_NAME, ACCOUNT_ID} = process.env\n\n\/\/ Create an SQS service object\nconst sqs = new AWS.SQS({apiVersion: '2012-11-05'});\n\nmodule.exports = async ({ owner, name, stars, lastUpdated, license}) =&gt; {\n  return new Promise((resolve, reject) =&gt; {\n    sqs.sendMessage({\n      DelaySeconds: 10,\n      MessageBody: JSON.stringify({\n        owner,\n        name,\n        stars,\n        license,\n        lastUpdated\n      }),\n      QueueUrl: `https:\/\/sqs.us-west-1.amazonaws.com\/${ACCOUNT_ID}\/${REPO_QUEUE_NAME}`\n    }, (err, data) =&gt; {\n      if (err) {\n        reject(err)\n        return\n      }\n      resolve(data)\n    })\n  })\n}<\/pre>\n<h2>Parsing repo<\/h2>\n<p>This step also took several iterations to get right. In my prototype I used a modified version of the Elm compiler which spits out all the symbols it discovered during compilation. Alex, who I met at the Elm Meetup in SF helped me with that. This worked well enough for the prototype but it wasn&#8217;t a stable solution. First of all I would need to maintain a fork of the compiler, which would be hard for me since I barely know any Haskell.<\/p>\n<p>Next I tried to use the <a href=\"https:\/\/github.com\/Bogdanp\/elm-ast\">elm-ast<\/a> library which is a Elm parser implemented in Elm. This looked promising at first and also had the benefit that I could easily run it in node directly because all Elm code compiles to Javascript. But I also ran into some some issues:<\/p>\n<ol>\n<li>the library didn&#8217;t cover all edge cases of the elm syntax. Some valid Elm files would lead to parsing errors.<\/li>\n<li>Positional information which maps the symbols back to the sourcefile was only rudimentarily implemented and had some bugs<\/li>\n<li>the performance was really slow. Sometimes it took up to several seconds to parse a single file.<\/li>\n<\/ol>\n<p>Especially the performance issue combined with the fact that the repo wasn&#8217;t actively maintained made the elm-ast library not usable either<\/p>\n<p>In the end I landed on the <a href=\"https:\/\/github.com\/avh4\/elm-format\">elm-format<\/a> library. It&#8217;s a code formatting library that automatically formats your Elm code. Brian, who I met at elm Europe introduced me to Aaron the creator of the library. They were already working on adding a flag which export the syntax tree for a file, but the efforts have been pushed back because so far there hasn&#8217;t been really a concrete usecase for this. Araon also added positional information. This problem had only one drawback: The library was written in haskell, which meant I had to run custom binaries inside of AWS Lambda.<\/p>\n<h2>Running Haskell code inside of AWS Lambda<\/h2>\n<p>In principle you can run any binary inside of an AWS Lambda function. The problem is that the environment is an extremely trimmed down Linux. Most binaries depend on external libraries which are dynamically linked. If you run such a binary inside of a lambda function it won&#8217;t work because the external libraries are missing.<\/p>\n<p>This was also the case with the elm-format binaries. I had to build the binaries myself with all the dependencies linked statically. For this I had to modify the cabal config file of elm-format<\/p>\n<pre class=\"prettyprint lang-yaml\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">executable elm-format-0.19\n\n    ghc-options:\n        -threaded -O2 -Wall -Wno-name-shadowing\n\n    hs-source-dirs:\n        src-cli\n\n    main-is:\n        Main0_19.hs\n\n    build-depends:\n        base &gt;= 4.9.0.0 &amp;&amp; &lt; 5,\n        elm-format\n\n    ld-options: -static \/\/ added static option\n\nexecutable elm-format-0.18\n\n    ghc-options:\n        -threaded -O2 -Wall -Wno-name-shadowing\n\n    hs-source-dirs:\n        src-cli\n\n    main-is:\n        Main0_18.hs\n\n    build-depends:\n        base &gt;= 4.9.0.0 &amp;&amp; &lt; 5,\n        elm-format\n\n    ld-options: -static \/\/ added static option<\/pre>\n<p>After that I build the binary from source.<\/p>\n<pre class=\"prettyprint lang-plain_text\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">stack install --ghc-options=\"-fPIC\"<\/pre>\n<p>At first I tried to run it on my MacBook which didn&#8217;t work because MacOS doesn&#8217;t provide a statically linkable version for all libraries. I solved this problem by quickly spinning up a Linux VM on Digital Ocean and building elm-format there. This illustrates nicely that the cloud is not only useful for scalable deployments but can also help during development.<\/p>\n<h2>Writing results to the database<\/h2>\n<p>Finally we need a Postgres database which stores all the references. We can add this as another resource in our serverless.yml file. We also need to create a SecurityGroup for the Database which makes the database accessible. Right now I&#8217;m using hardcoded values for the credentials. A better solutions would be to use the <a href=\"https:\/\/docs.aws.amazon.com\/systems-manager\/latest\/APIReference\/Welcome.html\">AWS Systems Manager<\/a> to store the credentials.<\/p>\n<pre class=\"prettyprint lang-yaml\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">pgSecurityGroup:\n    Type: AWS::EC2::SecurityGroup\n    Properties:\n      GroupDescription: Acess to Postgres\n      SecurityGroupIngress:\n      - IpProtocol: tcp\n        FromPort: '5432'\n        ToPort: '5432'\n        CidrIp: 0.0.0.0\/0\n\n  pgDB:\n    Type: \"AWS::RDS::DBInstance\"\n    Properties:\n      DBName: \"elmFunctionSearch\"\n      AllocatedStorage: 5\n      DBInstanceClass: \"db.t2.micro\"\n      Engine: \"postgres\"\n      EngineVersion: \"9.5.4\"\n      MasterUsername: \"master\"\n      MasterUserPassword: \"test12345\"\n      VPCSecurityGroups:\n      - Fn::GetAtt:\n        - pgSecurityGroup\n        - GroupId\n    DeletionPolicy: \"Delete\"<\/pre>\n<p>We also need to add the credentials and the host of the db to our environment so we can access the database later<\/p>\n<pre class=\"prettyprint lang-yaml\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">DATABASE_HOST:\n      Fn::GetAtt:\n        - pgDB\n        - Endpoint.Address\nDATABASE_USER: \"master\" \nDATABASE_SECRET: \"test12345\"\nDATABASE_NAME: \"elmFunctionSearch\"<\/pre>\n<p>Inside our application we can then construct a connection string to connect with the database<\/p>\n<pre class=\"prettyprint lang-javascript\" data-start-line=\"1\" data-visibility=\"visible\" data-highlight=\"\" data-caption=\"\">const {DATABASE_HOST, DATABASE_USER, DATABASE_SECRET, DATABASE_NAME} = process.env\n\nconst connectionString = \n    `postgres:\/\/${DATABASE_USER}:${DATABASE_SECRET}@${DATABASE_HOST}:5432\/${DATABASE_NAME}`<\/pre>\n<h2>Final Result<\/h2>\n<p>The search is very basic at the moment. The user can enter the name of a function.<\/p>\n<p><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03.png\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"4207\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/screenshot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29-23-03\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03.png\" data-orig-size=\"1500,376\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03-1024x257.png\" class=\"wp-image-4207 aligncenter\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03-300x75.png\" alt=\"\" width=\"304\" height=\"76\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03-300x75.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03-768x193.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03-1024x257.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-d2e234ba-5ef7-492b-a332-76c7f46f1b29.23.03.png 1500w\" sizes=\"auto, (max-width: 304px) 100vw, 304px\" \/><\/a><\/p>\n<p>If there are multiple matching functions the user can select which package they meant.<\/p>\n<p><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26.png\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"4208\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/screenshot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069-21-26\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26.png\" data-orig-size=\"1516,1548\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26-1003x1024.png\" class=\"size-medium wp-image-4208 aligncenter\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26-294x300.png\" alt=\"\" width=\"294\" height=\"300\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26-294x300.png 294w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26-768x784.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26-1003x1024.png 1003w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-4bd45192-ad45-463f-a4ab-533a7144b069.21.26.png 1516w\" sizes=\"auto, (max-width: 294px) 100vw, 294px\" \/><\/a><\/p>\n<p>The result is a list of links to github files which use the function<\/p>\n<p><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07.png\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"4209\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2018\/09\/01\/parsing-all-open-source-elm-code\/screenshot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab-21-07\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07.png\" data-orig-size=\"1520,1008\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07-1024x679.png\" class=\"size-medium wp-image-4209 aligncenter\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07-300x199.png\" alt=\"\" width=\"300\" height=\"199\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07-300x199.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07-768x509.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07-1024x679.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2018\/09\/ScreenShot2018-09-01at01-f3de4958-0d49-4473-b122-b29291d12dab.21.07.png 1520w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This project was originally inspired by a talk Felipe Hoffa gave at the Github Universe conference last year. He talked about how we can analyse the code hosted on Github at a large scale to learn interesting things. I&#8217;m always excited about learning new programming languages, at the moment my favourite new langue is Elm, [&hellip;]<\/p>\n","protected":false},"author":891,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1,22,657],"tags":[],"ppma_author":[769],"class_list":["post-4202","post","type-post","status-publish","format-standard","hentry","category-allgemein","category-student-projects","category-teaching-and-learning"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":1729,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2016\/12\/08\/snakes-exploring-pipelines-a-system-engineering-and-management-project-2\/","url_meta":{"origin":4202,"position":0},"title":"Snakes exploring Pipelines &#8211; A \u201cSystem Engineering and Management\u201d Project","author":"Yann Loic Philippczyk","date":"8. December 2016","format":false,"excerpt":"Part 1: Tool Setup This series of blog entries describes a student project focused on developing an application by using methods like pair programming, test driven development and deployment pipelines. Welcome to the next part our project, on its way to become the Snake game with the very best underlying\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/12\/blue-krait.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/12\/blue-krait.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/12\/blue-krait.jpg?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":21064,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/09\/11\/how-do-you-get-a-web-application-into-the-cloud\/","url_meta":{"origin":4202,"position":1},"title":"How do you get a web application into the cloud?","author":"af094","date":"11. September 2021","format":false,"excerpt":"by Dominik Ratzel (dr079) and Alischa Fritzsche (af094) For the lecture \"Software Development for Cloud Computing\", we set ourselves the goal of exploring new things and gaining experience. We focused on one topic: \"How do you get a web application into the cloud?\". In doing so, we took a closer\u2026","rel":"","context":"In &quot;Cloud Technologies&quot;","block_context":{"text":"Cloud Technologies","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/cloud-technologies\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/availableRunners-150x118.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":623,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2016\/06\/10\/test-driven-development-part-ii\/","url_meta":{"origin":4202,"position":2},"title":"Test Driven Development Part II","author":"Matthias Schmidt","date":"10. June 2016","format":false,"excerpt":"[written by Roman Kollatschny and Matthias Schmidt] Welcome back to the second article in our Node.js development series. Today, we are going to adapt the TDD cycle in an helloWorld example application. If you missed our first article about the principles of TDD, you can find it here. In the\u2026","rel":"","context":"In &quot;System Designs&quot;","block_context":{"text":"System Designs","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":28117,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/27\/how-to-develop-notification-system-for-crypto-stocks\/","url_meta":{"origin":4202,"position":3},"title":"How to Develop a Notification System for Crypto Stocks for Telegram and Discord","author":"Julia Bai","date":"27. February 2026","format":false,"excerpt":"This blog post was written for the lecture \"System Engineering & Management\" (143101a) by Julia Bai, Frederik Runge and Dominik Seitz. Introduction The cryptocurrency market never sleeps. While traditional stock exchanges close, trading in digital assets occurs 24\/7, characterized by extreme volatility where minutes decide between profit and loss. A\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Shift-Left20Defect20Detection20and20Remediation_5.gif?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Shift-Left20Defect20Detection20and20Remediation_5.gif?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Shift-Left20Defect20Detection20and20Remediation_5.gif?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Shift-Left20Defect20Detection20and20Remediation_5.gif?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":1758,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2017\/01\/12\/snakes-exploring-pipelines-a-system-engineering-and-management-project-5\/","url_meta":{"origin":4202,"position":4},"title":"Snakes exploring Pipelines &#8211; A \u201cSystem Engineering and Management\u201d Project","author":"Yann Loic Philippczyk","date":"12. January 2017","format":false,"excerpt":"Part 4: Jenkins and Wrap Up This series of blog entries describes a student project focused on developing an application by using methods like pair programming, test driven development and deployment pipelines. Our first blog entry for this year will at the same time be the final one for this\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/01\/jenkins_code-1.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/01\/jenkins_code-1.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/01\/jenkins_code-1.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":2651,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2017\/08\/28\/how-we-integrated-ibm-watson-services-into-a-telegram-chat-bot\/","url_meta":{"origin":4202,"position":5},"title":"How we integrated IBM Watson services into a Telegram chat bot","author":"Adrian Steinert, Oliver Speck, Megan Klaiber","date":"28. August 2017","format":false,"excerpt":"Introduction IBMs artificial intelligence \u2018Watson\u2019 on the IBM Bluemix platform offers a wide range of cognitive services like image and audio analysis among other things. During our semester project in the lecture \u2018Software Development for Cloud Computing\u2019 we integrated useful Watson services into a Telegram chat bot to provide a\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/12-factor.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/12-factor.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/12-factor.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]}],"jetpack_sharing_enabled":true,"authors":[{"term_id":769,"user_id":891,"is_guest":0,"slug":"ps096","display_name":"ps096","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/f3c19e79b6b63876eeb5d3afc3052714feb5c0b235293c3d8f39628bc3e7b666?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/4202","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/users\/891"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/comments?post=4202"}],"version-history":[{"count":4,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/4202\/revisions"}],"predecessor-version":[{"id":24772,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/4202\/revisions\/24772"}],"wp:attachment":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media?parent=4202"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/categories?post=4202"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/tags?post=4202"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/ppma_author?post=4202"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}