Introduction

Today (June 14, 2021) I answered a question on Stackoverflow regarding the Google Cloud Recommender API that required Python source code. I also created a GitHub Gist to make downloading the code easier. That got me thinking about how to display a Gist on a Laravel page. A Google search showed nothing that I could develop or learn from. This article is the results of experimenting with GitHub Gists and Laravel.

The final result is a one-line directive @gist that will display a GitHub Gist in a view.

Creating a GitHub Gist with the CLI

It is easy to publish a file as a public Gist. GitHub recently released the GitHub CLI which makes publishing a Gist a single line command.

Command syntax:

gh gist create [<filename>... | -] [flags]

Options:

-d, --desc string       A description for this gist
-f, --filename string   Provide a filename to be used when reading from STDIN
-p, --public            List the gist publicly (default: private)
-w, --web               Open the web browser with created gist

Example:

I put the code for my answer in the file recommender_example.py and published it with this command:

gh gist create recommender_example.py --public -d "Python example demonstrating the Google Cloud Compute Recommender list recommendations api"

Now, I have a public Gist. How do I display it?

Method #1 – Embedded Script

Click on the Gist URL. Above the gist is the option to grab the “Embed” script. For my gist the scripts is:

<script src="https://gist.github.com/jhanley-com/0aa83779df6fafdacd9fbcc636549d11.js"></script>

Let’s put that script into a basic HTML file and test. Start with a basic HTML 5 template and modify it as shown below. Notice the script tag at line 10. Save the following HTML as test1.html and display it in your browser. For Windows, from the Command Prompt, start test1.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My Gist</title>
  <meta name="description" content="My Gist">
  <meta name="author" content="www2.jhanley.com">
</head>
<body>
  <script src="https://gist.github.com/jhanley-com/0aa83779df6fafdacd9fbcc636549d11.js"></script>
</body>
</html>

Method #2 – Fetch Gist JSON

If you append .json to the end of a GitHub Gist URL, JSON is returned with details on the Gist. For example, load this URL into your browser:

https://gist.github.com/03c77114c5fb60c8fbf9dbd3749ca0e1.json

This returns the following JSON. Note that I have truncated the <div> portion to make the JSON readable.

{
  "description": "Simple GitHub Gist",
  "public": true,
  "created_at": "2021-06-15T05:14:55.000Z",
  "files": [
    "simple_gist.txt"
  ],
  "owner": "jhanley-com",
  "div": "<div>CONTENTS TRUNCATED</div>\n",
  "stylesheet": "https://github.githubassets.com/assets/gist-embed-6fe69c56f04992617d5f1a23949c2605.css"
}

Two of the keys are important:

  • stylesheet – This is the CSS markup that GitHub provides to beautify the HTML.
  • div – This is raw HTML to display on your page.

Let’s write a simple PHP program to read the JSON and output an HTML page. Copy the following code to index.php.

<?php

$url = 'https://gist.github.com/jhanley-com/03c77114c5fb60c8fbf9dbd3749ca0e1.json';

$results = file_get_contents($url, true);
$json = json_decode($results, true);

$html  = '<head>';
$html .= '<link rel="stylesheet" href="' . $json['stylesheet'] . '">';
$html .= '</head>';
$html .= '<body>';
$html .= $json['div'];
$html .= '</body>';

echo $html;

Start the built-in web server:

php -S localhost:8000

Start a web browser and go to http://localhost:8000/

Method #3 – Simple Laravel View Blade

Laravel blades support the @php directive to support PHP code. Let’s put together a simple view and a route to that view. The GitHub Gist is hardcoded in this example. Save this page to /resources/views/method3.blade.php. In this example, the Gist is a simple two-line file to simplify testing.

@php
$url = 'https://gist.github.com/03c77114c5fb60c8fbf9dbd3749ca0e1.json';

$results = file_get_contents($url, true);
$json = json_decode($results, true);

echo '<head>';
echo '<link rel="stylesheet" href="' . $json['stylesheet'] . '">';
echo '</head>';
echo '<body>';
echo $json['div'];
echo '</body>';
@endphp

Edit /routes/web.php and add the following route:

Route::get('/method3', function () {
    return view('method3');
});

when developing, clear the route cache after each route change:

php artisan route:clear
php artisan route:cache

Start the Laravel built-in development server:

php artisan serve

Start a web browser and go to http://localhost:8000/method3

We can enhance this by passing the gist URL as data to the view:

@php
$results = file_get_contents($gist_url, true);
$json = json_decode($results, true);

echo '<head>';
echo '<link rel="stylesheet" href="' . $json['stylesheet'] . '">';
echo '</head>';
echo '<body>';
echo $json['div'];
echo '</body>';
@endphp

Route:

Route::get('/method3', function () {
    $gist_url = 'https://gist.github.com/jhanley-com/0aa83779df6fafdacd9fbcc636549d11.json';
    return view('method3', ["gist_url" => $gist_url]);
});

Method #4 – Custom Directive

You can define your own custom directives using the directive method. In this example, we will extend Laravel with the @gist directive.

Edit app/Providers/AppServiceProvider.php and include the following code:

        public function boot()
        {
                Blade::directive('gist', function($gist_url) {
                        $php = <<<'PHP'
$results = file_get_contents($gist_url, true);
$json = json_decode($results, true);
echo '<link type="text/css" rel="stylesheet" href="' . $json['stylesheet'] . '">';
echo $json['div'];
PHP;
                        return "<?php $php ?>";
                });
        }

Now you can display a Gist with a directive in the view. Put the following line in a new file /resources/views/method4.blade.php

@gist($gist_url)

Create a route:

Route::get('/method4', function () {
    $gist_url = 'https://gist.github.com/jhanley-com/0aa83779df6fafdacd9fbcc636549d11.json';
    return view('method4', ["gist_url" => $gist_url]);
});

Each time you modify AppServiceProvider.php or the view, clear the view cache:

php artisan view:clear

Start a web browser and go to http://localhost:8000/method4

Controlling Gist Width and Height

Use the following CSS as a starter for controlling the height and width of a Gist:

  <style type="text/css">
    .gist {
       overflow:auto;
       width:400px;
    }

    .gist .blob-wrapper.data {
       max-width:400px;
       max-height:400px;
       overflow:auto;
    }
  </style>

Listing a user’s public Gists

GitHub offers an API to list a user’s public Gists. Let’s test this using curl and jq. The API returns a lot of information. We are interested in the public URL. This example sends the JSON output to jq to parse the public URL.

List gists for a user

You will need the user’s username. In my case it is jhanley-com. The username is easy to find. Find a user’s repository or Gist and the username is in the URL. Example for Google: https://github.com/google.

curl https://api.github.com/users/jhanley-com/gists | jq -r ".[].html_url"

Example output:

https://gist.github.com/03c77114c5fb60c8fbf9dbd3749ca0e1
# pip install google-cloud-recommender
from google.cloud import recommender
import os
# Enter values for your Project ID and Zone
PROJECT=
LOCATION=
RECOMMENDER = 'google.compute.instance.MachineTypeRecommender'
def print_recommendations():
client = recommender.RecommenderClient()
parent = client.recommender_path(PROJECT, LOCATION, RECOMMENDER)
recommendations = client.list_recommendations(parent=parent)
for recommendation in recommendations:
print('Recommendation:')
print(' Description: ', recommendation.description)
print(' Impact Category: ', recommendation.primary_impact.category)
print(' Currency Code: ', recommendation.primary_impact.cost_projection.cost.currency_code)
print(' Units: ', recommendation.primary_impact.cost_projection.cost.units)
print(' State: ', recommendation.state_info.state)
print('')
for op_group in recommendation.content._pb.operation_groups:
for operation in op_group.operations:
print(' Operation:')
print(' Action: ', operation.action)
print(' Resource Type: ', operation.resource_type)
print(' Path: ', operation.path)
print(' Value: ', operation.value.string_value)
print('')
print_recommendations()
# This script is used in my article about Terraform, Google Cloud DNS and Cloud IAM.
# https://www.jhanley.com/terraform-experiments-with-google-cloud-dns-and-iam/
######################################################################
# Terraform
######################################################################
terraform {
required_version = ">= 0.14.7"
}
######################################################################
# Google Cloud Provider
######################################################################
provider "google" {
credentials = var.gcp_service_account
project = var.gcp_project
}
######################################################################
# variables for Google Cloud Provider
######################################################################
variable "gcp_project" {
description = "Google Cloud Project ID"
}
variable "gcp_service_account" {
description = "Service Account filename for Authorization"
}
######################################################################
# variables for the Google Cloud DNS Managed Zone
######################################################################
variable "zonename" {
description = "Google Cloud DNS Zone Name (not the domain name)"
default = "myexample-com"
}
######################################################################
# access the Cloud DNS Zone attributes
######################################################################
data "google_dns_managed_zone" "dns_zone" {
name = var.zonename
}
######################################################################
#
######################################################################
output "gcp_dns_zone" {
value = data.google_dns_managed_zone.dns_zone
}
view raw main.tf hosted with ❤ by GitHub
IF exist .terraform (rd /s /q .terraform)
IF exist terraform.tfstate.d (rd /s /q terraform.tfstate.d)
IF exist .terraform.lock.hcl (del .terraform.lock.hcl)
IF exist terraform.tfstate (del terraform.tfstate)
IF exist terraform.tfstate.backup (del terraform.tfstate.backup)
view raw tclean.bat hosted with ❤ by GitHub
# Test code for the question: https://stackoverflow.com/q/65525116/8016720
# Provides configuration details for Terraform
terraform {
required_version = ">= 0.14.3, < 0.15.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>2.41"
}
}
}
# Provides configuration details for the Azure Terraform provider
provider "azurerm" {
# !!! Must include features even if empty
features {}
subscription_id = ""
client_id = ""
client_secret = "long-random-string"
tenant_id = ""
}
# Provides the Resource Group to logically contain resources
resource "azurerm_resource_group" "rg" {
name = "terraform-test-rg-4"
location = "westus2"
tags = {
environment = "dev"
source = "Terraform"
owner = "John Hanley"
}
}
view raw main.tf hosted with ❤ by GitHub
# Test code for the question: https://stackoverflow.com/q/65525116/8016720
# Provides configuration details for the Azure Terraform provider
provider "azurerm" {
# !!! Must include features even if empty
features {}
}
variable "appName" { default = "testAppName" }
variable "subscriptionId" { default = "" }
resource "azuread_application" "appReg" {
name = var.appName
}
resource "azuread_service_principal" "example-sp" {
application_id = azuread_application.appReg.application_id
}
resource "azuread_service_principal_password" "example-sp_pwd" {
service_principal_id = azuread_service_principal.example-sp.id
value = "long-random-string"
end_date = "2021-06-02T01:02:03Z"
}
data "azurerm_subscription" "thisSubscription" {
subscription_id = var.subscriptionId
}
resource "azurerm_role_assignment" "example-sp_role_assignment" {
scope = data.azurerm_subscription.thisSubscription.id
role_definition_name = "Contributor"
principal_id = azuread_service_principal.example-sp.id
}
resource "azuread_application_app_role" "example-role" {
application_object_id = azuread_application.appReg.id
allowed_member_types = ["User", "Application"]
description = "Admins can manage roles and perform all task actions"
display_name = "Admin"
is_enabled = true
value = "administer"
}
output "application_id" {
value = azuread_application.appReg.application_id
}
output "appId" {
value = azuread_service_principal.example-sp.application_id
}
view raw main.tf hosted with ❤ by GitHub
APIKEY='your_cloud_api_key'
URI='https://clients.hostwinds.com/cloud/api.php'
FILENAME='instances.json'
if [ -f $FILENAME ];
then
rm $FILENAME
fi
curl $URI \
-sS \
-X POST \
-d "action=get_instances&API=$APIKEY" > $FILENAME
status=$?
if test $status -eq 0
then
# Print instance names
echo "Instances"
echo "IP OS ServerName Hostname"
echo "----------------------------------------------------------------------"
cat $FILENAME | jq -r ".success | .[] | [.main_ip, .image.name, .srvrname, .hostname] | @tsv" 2>error.log
status=$?
if test $status -eq 0
then
echo
echo "------------------------------"
echo Success
else
echo Failed
cat $FILENAME | jq
fi
fi
@set APIKEY=your_cloud_api_key
@set URI=https://clients.hostwinds.com/cloud/api.php
@set FILENAME=instances.json
@if exist %FILENAME% del %FILENAME%
@curl %URI% ^
-sS ^
-X POST ^
-d "action=get_instances&API=%APIKEY%" > %FILENAME%
@if %errorlevel% equ 0 (
@REM - Print instance names
@echo Instances
@echo IP OS ServerName Hostname
@echo ----------------------------------------------------------------------
@cat %FILENAME% | jq -r ".success | .[] | [.main_ip, .image.name, .srvrname, .hostname] | @tsv" 2>error.log
@if %errorlevel% equ 0 (
@echo.
@echo ------------------------------
@echo Success
) else (
@REM @echo Exit Code is %errorlevel%
@echo Failed
@cat %FILENAME% | jq
)
)

Summary

Once you understand how to display a Gist in a view, it is easy to style the Gist by wrapping it with HTML and CSS. You can control placement, width, height, and more easily.