Merge branch 'main' into main
This commit is contained in:
commit
12a1a1beb7
8
_extensions/shafayetShafee/downloadthis/_extension.yml
Normal file
8
_extensions/shafayetShafee/downloadthis/_extension.yml
Normal file
@ -0,0 +1,8 @@
|
||||
title: Downloadthis
|
||||
author: Shafayet Khan Shafee
|
||||
version: 1.1.0
|
||||
quarto-required: ">=1.2.0"
|
||||
contributes:
|
||||
shortcodes:
|
||||
- downloadthis.lua
|
||||
|
121
_extensions/shafayetShafee/downloadthis/downloadthis.lua
Normal file
121
_extensions/shafayetShafee/downloadthis/downloadthis.lua
Normal file
@ -0,0 +1,121 @@
|
||||
--[[
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Shafayet Khan Shafee
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
]]--
|
||||
|
||||
|
||||
|
||||
local str = pandoc.utils.stringify
|
||||
--local p = quarto.log.output
|
||||
|
||||
local function ensureHtmlDeps()
|
||||
quarto.doc.add_html_dependency({
|
||||
name = "downloadthis",
|
||||
version = "1.9.1",
|
||||
stylesheets = {"resources/css/downloadthis.css"}
|
||||
})
|
||||
end
|
||||
|
||||
local function optional(arg, default)
|
||||
if arg == nil or arg == ""
|
||||
then
|
||||
return default
|
||||
else
|
||||
return arg
|
||||
end
|
||||
end
|
||||
|
||||
function import(script)
|
||||
local path = PANDOC_SCRIPT_FILE:match("(.*[/\\])")
|
||||
package.path = path .. script .. ";" .. package.path
|
||||
return require(script)
|
||||
end
|
||||
|
||||
local puremagic = import("puremagic.lua")
|
||||
|
||||
return {
|
||||
['downloadthis'] = function(args, kwargs, meta)
|
||||
|
||||
-- args and kwargs
|
||||
local file_path = str(args[1])
|
||||
local extension = "." .. file_path:match("[^.]+$")
|
||||
local dname = optional(str(kwargs["dname"]), "file")
|
||||
local dfilename = dname .. extension
|
||||
local btn_label = " " .. optional(str(kwargs["label"]), "Download") .. " "
|
||||
local btn_type = optional(str(kwargs["type"]), "default")
|
||||
local icon = optional(str(kwargs["icon"]), "download")
|
||||
local class = " " .. optional(str(kwargs["class"]), "")
|
||||
local rand = "dnldts" .. str(math.random(1, 65000))
|
||||
local id = optional(str(kwargs["id"]), rand)
|
||||
-- reading files
|
||||
local fh = io.open(file_path, "rb")
|
||||
if not fh then
|
||||
io.stderr:write("Cannot open file " ..
|
||||
file_path ..
|
||||
" | Skipping adding buttons\n")
|
||||
return pandoc.Null()
|
||||
else
|
||||
local contents = fh:read("*all")
|
||||
fh:close()
|
||||
|
||||
-- creating dataURI object
|
||||
local b64_encoded = quarto.base64.encode(contents)
|
||||
local mimetype = puremagic.via_path(file_path)
|
||||
local data_uri = 'data:' .. mimetype .. ";base64," .. b64_encoded
|
||||
|
||||
-- js code taken from
|
||||
-- https://github.com/fmmattioni/downloadthis/blob/master/R/utils.R#L59
|
||||
local js = [[fetch('%s').then(res => res.blob()).then(blob => {
|
||||
const downloadURL = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
document.body.appendChild(a);
|
||||
a.href = downloadURL;
|
||||
a.download = '%s'; a.click();
|
||||
window.URL.revokeObjectURL(downloadURL);
|
||||
document.body.removeChild(a);
|
||||
});]]
|
||||
|
||||
local clicked = js:format(data_uri, dfilename)
|
||||
|
||||
-- creating button
|
||||
local button =
|
||||
"<button class=\"btn btn-" .. btn_type .. " downloadthis " ..
|
||||
class .. "\"" ..
|
||||
" id=\"" .. id .. "\"" ..
|
||||
"><i class=\"bi bi-" .. icon .. "\"" .. "></i>" ..
|
||||
btn_label ..
|
||||
"</button>"
|
||||
if quarto.doc.is_format("html:js") and quarto.doc.has_bootstrap()
|
||||
then
|
||||
ensureHtmlDeps()
|
||||
return pandoc.RawInline('html',
|
||||
"<a href=\"#" .. id .. "\"" ..
|
||||
" onclick=\"" .. clicked .. "\">" .. button .. "</a>"
|
||||
)
|
||||
else
|
||||
return pandoc.Null()
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
|
735
_extensions/shafayetShafee/downloadthis/puremagic.lua
Normal file
735
_extensions/shafayetShafee/downloadthis/puremagic.lua
Normal file
@ -0,0 +1,735 @@
|
||||
-- puremagic 1.0.1
|
||||
-- Copyright (c) 2014 Will Bond <will@wbond.net>
|
||||
-- Licensed under the MIT license.
|
||||
|
||||
|
||||
function basename(path)
|
||||
local basename_match = path:match('[/\\]([^/\\]+)$')
|
||||
if basename_match then
|
||||
return basename_match, nil
|
||||
end
|
||||
|
||||
return path, nil
|
||||
end
|
||||
|
||||
|
||||
function extension(path)
|
||||
path = path:lower()
|
||||
local tar_match = path:match('%.(tar%.[^.]+)$')
|
||||
if tar_match then
|
||||
return tar_match
|
||||
end
|
||||
if path:sub(#path - 11, #path) == '.numbers.zip' then
|
||||
return 'numbers.zip'
|
||||
end
|
||||
if path:sub(#path - 9, #path) == '.pages.zip' then
|
||||
return 'pages.zip'
|
||||
end
|
||||
if path:sub(#path - 7, #path) == '.key.zip' then
|
||||
return 'key.zip'
|
||||
end
|
||||
return path:match('%.([^.]+)$')
|
||||
end
|
||||
|
||||
|
||||
function in_table(value, list)
|
||||
for i=1, #list do
|
||||
if list[i] == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function string_to_bit_table(chars)
|
||||
local output = {}
|
||||
for char in chars:gmatch('.') do
|
||||
local num = string.byte(char)
|
||||
local bits = {0, 0, 0, 0, 0, 0, 0, 0}
|
||||
for bit=8, 1, -1 do
|
||||
if num > 0 then
|
||||
bits[bit] = math.fmod(num, 2)
|
||||
num = (num - bits[bit]) / 2
|
||||
end
|
||||
end
|
||||
table.insert(output, bits)
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
|
||||
function bit_table_to_string(bits)
|
||||
local output = {}
|
||||
for i = 1, #bits do
|
||||
local num = tonumber(table.concat(bits[i]), 2)
|
||||
table.insert(output, string.format('%c', num))
|
||||
end
|
||||
return table.concat(output)
|
||||
end
|
||||
|
||||
|
||||
function bitwise_and(a, b)
|
||||
local a_bytes = string_to_bit_table(a)
|
||||
local b_bytes = string_to_bit_table(b)
|
||||
|
||||
local output = {}
|
||||
for i = 1, #a_bytes do
|
||||
local bits = {0, 0, 0, 0, 0, 0, 0, 0}
|
||||
for j = 1, 8 do
|
||||
if a_bytes[i][j] == 1 and b_bytes[i][j] == 1 then
|
||||
bits[j] = 1
|
||||
else
|
||||
bits[j] = 0
|
||||
end
|
||||
end
|
||||
table.insert(output, bits)
|
||||
end
|
||||
|
||||
return bit_table_to_string(output)
|
||||
end
|
||||
|
||||
|
||||
-- Unpack a little endian byte string into an integer
|
||||
function unpack_le(chars)
|
||||
local bit_table = string_to_bit_table(chars)
|
||||
-- Merge the bits into a string of 1s and 0s
|
||||
local result = {}
|
||||
for i=1, #bit_table do
|
||||
result[#chars + 1 - i] = table.concat(bit_table[i])
|
||||
end
|
||||
return tonumber(table.concat(result), 2)
|
||||
end
|
||||
|
||||
|
||||
-- Unpack a big endian byte string into an integer
|
||||
function unpack_be(chars)
|
||||
local bit_table = string_to_bit_table(chars)
|
||||
-- Merge the bits into a string of 1s and 0s
|
||||
for i=1, #bit_table do
|
||||
bit_table[i] = table.concat(bit_table[i])
|
||||
end
|
||||
return tonumber(table.concat(bit_table), 2)
|
||||
end
|
||||
|
||||
|
||||
-- Takes the first 4-8k of an EBML file and identifies if it is matroska or webm
|
||||
-- and it it contains just video or just audio.
|
||||
function ebml_parse(content)
|
||||
local position = 1
|
||||
local length = #content
|
||||
|
||||
local header_token, header_value, used_bytes = ebml_parse_section(content)
|
||||
position = position + used_bytes
|
||||
|
||||
|
||||
if header_token ~= '\x1AE\xDF\xA3' then
|
||||
return nil, 'Unable to find EBML ID'
|
||||
end
|
||||
|
||||
-- The matroska spec sets the default doctype to be 'matroska', however
|
||||
-- many file specify this anyway. The other option is 'webm'.
|
||||
local doctype = 'matroska'
|
||||
if header_value['B\x82'] then
|
||||
doctype = header_value['B\x82']
|
||||
end
|
||||
|
||||
if doctype ~= 'matroska' and doctype ~= 'webm' then
|
||||
return nil, 'Unknown EBML doctype'
|
||||
end
|
||||
|
||||
local segment_position = nil
|
||||
local track_position = nil
|
||||
local has_video = false
|
||||
local found_tracks = false
|
||||
|
||||
while position <= length do
|
||||
local ebml_id, ebml_value, used_bytes = ebml_parse_section(content:sub(position, length))
|
||||
position = position + used_bytes
|
||||
|
||||
-- Segment
|
||||
if ebml_id == '\x18S\x80g' then
|
||||
segment_position = position
|
||||
end
|
||||
|
||||
-- Meta seek information
|
||||
if ebml_id == '\x11M\x9Bt' then
|
||||
-- Look for the seek info about the tracks token
|
||||
for i, child in ipairs(ebml_value['M\xBB']) do
|
||||
if child['S\xAB'] == '\x16T\xAEk' then
|
||||
track_position = segment_position + unpack_be(child['S\xAC'])
|
||||
position = track_position
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Track
|
||||
if ebml_id == '\x16T\xAEk' then
|
||||
found_tracks = true
|
||||
-- Scan through each track looking for video
|
||||
for i, child in ipairs(ebml_value['\xAE']) do
|
||||
-- Look to see if the track type is video
|
||||
if unpack_be(child['\x83']) == 1 then
|
||||
has_video = true
|
||||
break
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found_tracks and not has_video then
|
||||
if doctype == 'matroska' then
|
||||
return 'audio/x-matroska'
|
||||
else
|
||||
return 'audio/webm'
|
||||
end
|
||||
end
|
||||
|
||||
if doctype == 'matroska' then
|
||||
return 'video/x-matroska'
|
||||
else
|
||||
return 'video/webm'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Parses a section of an EBML document, returning the EBML ID at the beginning,
|
||||
-- plus the value as a table with child EBML IDs as keys and the number of
|
||||
-- bytes from the content that contained the ID and value
|
||||
function ebml_parse_section(content)
|
||||
local ebml_id, element_length, used_bytes = ebml_id_and_length(content)
|
||||
|
||||
-- Don't parse the segment since it is the whole file!
|
||||
if ebml_id == '\x18\x53\x80\x67' then
|
||||
return ebml_id, nil, used_bytes
|
||||
end
|
||||
|
||||
local ebml_value = content:sub(used_bytes + 1, used_bytes + element_length)
|
||||
used_bytes = used_bytes + element_length
|
||||
|
||||
-- We always parse the return value of level 0/1 elements
|
||||
local recursive_parse = false
|
||||
if #ebml_id == 4 then
|
||||
recursive_parse = true
|
||||
|
||||
-- We need Seek information
|
||||
elseif ebml_id == '\x4D\xBB' then
|
||||
recursive_parse = true
|
||||
|
||||
-- We want the top-level of TrackEntry to grab the TrackType
|
||||
elseif ebml_id == '\xAE' then
|
||||
recursive_parse = true
|
||||
end
|
||||
|
||||
if recursive_parse then
|
||||
local buffer = ebml_value
|
||||
ebml_value = {}
|
||||
|
||||
-- Track which child entries have been converted to an array
|
||||
local array_children = {}
|
||||
|
||||
while #buffer > 0 do
|
||||
local child_ebml_id, child_ebml_value, child_used_bytes = ebml_parse_section(buffer)
|
||||
|
||||
if array_children[child_ebml_id] then
|
||||
table.insert(ebml_value[child_ebml_id], child_ebml_value)
|
||||
|
||||
-- Single values are just stores by themselves
|
||||
elseif ebml_value[child_ebml_id] == nil then
|
||||
-- Force seek info and tracks to be arrays even if there is only one
|
||||
if child_ebml_id == 'M\xBB' or child_ebml_id == '\xAE' then
|
||||
child_ebml_value = {child_ebml_value}
|
||||
array_children[child_ebml_id] = true
|
||||
end
|
||||
ebml_value[child_ebml_id] = child_ebml_value
|
||||
|
||||
-- If there is already a value for the ID, turn it into a table
|
||||
else
|
||||
ebml_value[child_ebml_id] = {ebml_value[child_ebml_id], child_ebml_value}
|
||||
array_children[child_ebml_id] = true
|
||||
end
|
||||
|
||||
-- Move past the part we've parsed
|
||||
buffer = buffer:sub(child_used_bytes + 1, #buffer)
|
||||
end
|
||||
end
|
||||
|
||||
return ebml_id, ebml_value, used_bytes
|
||||
end
|
||||
|
||||
|
||||
-- Should accept 12+ bytes, will return the ebml id, the data length and the
|
||||
-- number of bytes that were used to hold those values.
|
||||
function ebml_id_and_length(chars)
|
||||
-- The ID is encoded the same way as the length, however, we don't want
|
||||
-- to remove the length bits from the ID value or intepret it as an
|
||||
-- unsigned int since all of the documentation online references the IDs in
|
||||
-- encoded form.
|
||||
local _, id_length = ebml_length(chars:sub(1, 4))
|
||||
local ebml_id = chars:sub(1, id_length)
|
||||
|
||||
local remaining = chars:sub(id_length + 1, id_length + 8)
|
||||
local element_length, used_bytes = ebml_length(remaining)
|
||||
|
||||
return ebml_id, element_length, id_length + used_bytes
|
||||
end
|
||||
|
||||
|
||||
-- Should accept 8+ bytes, will return the data length plus the number of bytes
|
||||
-- that were used to hold the data length.
|
||||
function ebml_length(chars)
|
||||
-- We substring chars to ensure we don't build a huge table we don't need
|
||||
local bit_tables = string_to_bit_table(chars:sub(1, 8))
|
||||
|
||||
local value_length = 1
|
||||
for i=1, #bit_tables[1] do
|
||||
if bit_tables[1][i] == 0 then
|
||||
value_length = value_length + 1
|
||||
else
|
||||
-- Clear the indicator bit so the rest of the byte
|
||||
bit_tables[1][i] = 0
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local bits = {}
|
||||
for i=1, value_length do
|
||||
table.insert(bits, table.concat(bit_tables[i]))
|
||||
end
|
||||
|
||||
return tonumber(table.concat(bits), 2), value_length
|
||||
end
|
||||
|
||||
|
||||
function binary_tests(content, ext)
|
||||
local length = #content
|
||||
local _1_8 = content:sub(1, 8)
|
||||
local _1_7 = content:sub(1, 7)
|
||||
local _1_6 = content:sub(1, 6)
|
||||
local _1_5 = content:sub(1, 5)
|
||||
local _1_4 = content:sub(1, 4)
|
||||
local _1_3 = content:sub(1, 3)
|
||||
local _1_2 = content:sub(1, 2)
|
||||
local _9_12 = content:sub(9, 12)
|
||||
|
||||
|
||||
-- Images
|
||||
if _1_4 == '\xC5\xD0\xD3\xC6' then
|
||||
-- With a Windows-format EPS, the file starts right after a 30-byte
|
||||
-- header, or a 30-byte header followed by two bytes of padding
|
||||
if content:sub(33, 42) == '%!PS-Adobe' or content:sub(31, 40) == '%!PS-Adobe' then
|
||||
return 'application/postscript'
|
||||
end
|
||||
end
|
||||
|
||||
if _1_8 == '%!PS-Ado' and content:sub(9, 10) == 'be' then
|
||||
return 'application/postscript'
|
||||
end
|
||||
|
||||
if _1_4 == 'MM\x00*' or _1_4 == 'II*\x00' then
|
||||
return 'image/tiff'
|
||||
end
|
||||
|
||||
if _1_8 == '\x89PNG\r\n\x1A\n' then
|
||||
return 'image/png'
|
||||
end
|
||||
|
||||
if _1_6 == 'GIF87a' or _1_6 == 'GIF89a' then
|
||||
return 'image/gif'
|
||||
end
|
||||
|
||||
if _1_4 == 'RIFF' and _9_12 == 'WEBP' then
|
||||
return 'image/webp'
|
||||
end
|
||||
|
||||
if _1_2 == 'BM' and length > 14 and in_table(content:sub(15, 15), {'\x0C', '(', '@', '\x80'}) then
|
||||
return 'image/x-ms-bmp'
|
||||
end
|
||||
|
||||
local normal_jpeg = length > 10 and in_table(content:sub(7, 10), {'JFIF', 'Exif'})
|
||||
local photoshop_jpeg = length > 24 and _1_4 == '\xFF\xD8\xFF\xED' and content:sub(21, 24) == '8BIM'
|
||||
if normal_jpeg or photoshop_jpeg then
|
||||
return 'image/jpeg'
|
||||
end
|
||||
|
||||
if _1_4 == '8BPS' then
|
||||
return 'image/vnd.adobe.photoshop'
|
||||
end
|
||||
|
||||
if _1_8 == '\x00\x00\x00\x0CjP ' and _9_12 == '\r\n\x87\n' then
|
||||
return 'image/jp2'
|
||||
end
|
||||
|
||||
if _1_4 == '\x00\x00\x01\x00' then
|
||||
return 'application/vnd.microsoft.icon'
|
||||
end
|
||||
|
||||
|
||||
-- Audio/Video
|
||||
if _1_4 == '\x1AE\xDF\xA3' and length > 1000 then
|
||||
local mimetype, err = ebml_parse(content)
|
||||
|
||||
if mimetype then
|
||||
return mimetype
|
||||
end
|
||||
end
|
||||
|
||||
if _1_4 == 'MOVI' then
|
||||
if in_table(content:sub(5, 8), {'moov', 'mdat'}) then
|
||||
return 'video/quicktime'
|
||||
end
|
||||
end
|
||||
|
||||
if length > 8 and content:sub(5, 8) == 'ftyp' then
|
||||
local lower_9_12 = _9_12:lower()
|
||||
|
||||
if in_table(lower_9_12, {'avc1', 'isom', 'iso2', 'mp41', 'mp42', 'mmp4', 'ndsc', 'ndsh', 'ndsm', 'ndsp', 'ndss', 'ndxc', 'ndxh', 'ndxm', 'ndxp', 'ndxs', 'f4v ', 'f4p ', 'm4v '}) then
|
||||
return 'video/mp4'
|
||||
end
|
||||
|
||||
if in_table(lower_9_12, {'msnv', 'ndas', 'f4a ', 'f4b ', 'm4a ', 'm4b ', 'm4p '}) then
|
||||
return 'audio/mp4'
|
||||
end
|
||||
|
||||
if in_table(lower_9_12, {'3g2a', '3g2b', '3g2c', 'kddi'}) then
|
||||
return 'video/3gpp2'
|
||||
end
|
||||
|
||||
if in_table(lower_9_12, {'3ge6', '3ge7', '3gg6', '3gp1', '3gp2', '3gp3', '3gp4', '3gp5', '3gp6', '3gs7'}) then
|
||||
return 'video/3gpp'
|
||||
end
|
||||
|
||||
if lower_9_12 == 'mqt ' or lower_9_12 == 'qt ' then
|
||||
return 'video/quicktime'
|
||||
end
|
||||
|
||||
if lower_9_12 == 'jp2 ' then
|
||||
return 'image/jp2'
|
||||
end
|
||||
end
|
||||
|
||||
-- MP3
|
||||
if bitwise_and(_1_2, '\xFF\xF6') == '\xFF\xF2' then
|
||||
local byte_3 = content:sub(3, 3)
|
||||
if bitwise_and(byte_3, '\xF0') ~= '\xF0' and bitwise_and(byte_3, "\x0C") ~= "\x0C" then
|
||||
return 'audio/mpeg'
|
||||
end
|
||||
end
|
||||
if _1_3 == 'ID3' then
|
||||
return 'audio/mpeg'
|
||||
end
|
||||
|
||||
if _1_4 == 'fLaC' then
|
||||
return 'audio/x-flac'
|
||||
end
|
||||
|
||||
if _1_8 == '0&\xB2u\x8Ef\xCF\x11' then
|
||||
-- Without writing a full-on ASF parser, we can just scan for the
|
||||
-- UTF-16 string "AspectRatio"
|
||||
if content:find('\x00A\x00s\x00p\x00e\x00c\x00t\x00R\x00a\x00t\x00i\x00o', 1, true) then
|
||||
return 'video/x-ms-wmv'
|
||||
end
|
||||
return 'audio/x-ms-wma'
|
||||
end
|
||||
|
||||
if _1_4 == 'RIFF' and _9_12 == 'AVI ' then
|
||||
return 'video/x-msvideo'
|
||||
end
|
||||
|
||||
if _1_4 == 'RIFF' and _9_12 == 'WAVE' then
|
||||
return 'audio/x-wav'
|
||||
end
|
||||
|
||||
if _1_4 == 'FORM' and _9_12 == 'AIFF' then
|
||||
return 'audio/x-aiff'
|
||||
end
|
||||
|
||||
if _1_4 == 'OggS' then
|
||||
local _29_33 = content:sub(29, 33)
|
||||
if _29_33 == '\x01vorb' then
|
||||
return 'audio/vorbis'
|
||||
end
|
||||
if _29_33 == '\x07FLAC' then
|
||||
return 'audio/x-flac'
|
||||
end
|
||||
if _29_33 == 'OpusH' then
|
||||
return 'audio/ogg'
|
||||
end
|
||||
-- Theora and OGM
|
||||
if _29_33 == '\x80theo' or _29_33 == 'vide' then
|
||||
return 'video/ogg'
|
||||
end
|
||||
end
|
||||
|
||||
if _1_3 == 'FWS' or _1_3 == 'CWS' then
|
||||
return 'application/x-shockwave-flash'
|
||||
end
|
||||
|
||||
if _1_3 == 'FLV' then
|
||||
return 'video/x-flv'
|
||||
end
|
||||
|
||||
|
||||
if _1_5 == '%PDF-' then
|
||||
return 'application/pdf'
|
||||
end
|
||||
|
||||
if _1_5 == '{\\rtf' then
|
||||
return 'text/rtf'
|
||||
end
|
||||
|
||||
|
||||
-- Office '97-2003 formats
|
||||
if _1_8 == '\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1' then
|
||||
if in_table(ext, {'xls', 'csv', 'tab'}) then
|
||||
return 'application/vnd.ms-excel'
|
||||
end
|
||||
if ext == 'ppt' then
|
||||
return 'application/vnd.ms-powerpoint'
|
||||
end
|
||||
-- We default to word since we need something if the extension isn't recognized
|
||||
return 'application/msword'
|
||||
end
|
||||
|
||||
if _1_8 == '\x09\x04\x06\x00\x00\x00\x10\x00' then
|
||||
return 'application/vnd.ms-excel'
|
||||
end
|
||||
|
||||
if _1_6 == '\xDB\xA5\x2D\x00\x00\x00' or _1_5 == '\x50\x4F\x5E\x51\x60' or _1_4 == '\xFE\x37\x00\x23' or _1_3 == '\x94\xA6\x2E' then
|
||||
return 'application/msword'
|
||||
end
|
||||
|
||||
if _1_4 == 'PK\x03\x04' then
|
||||
-- Office XML formats
|
||||
if ext == 'xlsx' then
|
||||
return 'application/vnd.ms-excel'
|
||||
end
|
||||
|
||||
if ext == 'pptx' then
|
||||
return 'application/vnd.ms-powerpoint'
|
||||
end
|
||||
|
||||
if ext == 'docx' then
|
||||
return 'application/msword'
|
||||
end
|
||||
|
||||
-- Open Office formats
|
||||
if ext == 'ods' then
|
||||
return 'application/vnd.oasis.opendocument.spreadsheet'
|
||||
end
|
||||
|
||||
if ext == 'odp' then
|
||||
return 'application/vnd.oasis.opendocument.presentation'
|
||||
end
|
||||
|
||||
if ext == 'odt' then
|
||||
return 'application/vnd.oasis.opendocument.text'
|
||||
end
|
||||
|
||||
-- iWork - some programs like Mac Mail change the filename to
|
||||
-- .numbers.zip, etc
|
||||
if ext == 'pages' or ext == 'pages.zip' then
|
||||
return 'application/vnd.apple.pages'
|
||||
end
|
||||
if ext == 'key' or ext == 'key.zip' then
|
||||
return 'application/vnd.apple.keynote'
|
||||
end
|
||||
if ext == 'numbers' or ext == 'numbers.zip' then
|
||||
return 'application/vnd.apple.numbers'
|
||||
end
|
||||
|
||||
-- Otherwise just a zip
|
||||
return 'application/zip'
|
||||
end
|
||||
|
||||
|
||||
-- Archives
|
||||
if length > 257 then
|
||||
if content:sub(258, 263) == 'ustar\x00' then
|
||||
return 'application/x-tar'
|
||||
end
|
||||
if content:sub(258, 265) == 'ustar\x40\x40\x00' then
|
||||
return 'application/x-tar'
|
||||
end
|
||||
end
|
||||
|
||||
if _1_7 == 'Rar!\x1A\x07\x00' or _1_8 == 'Rar!\x1A\x07\x01\x00' then
|
||||
return 'application/x-rar-compressed'
|
||||
end
|
||||
|
||||
if _1_2 == '\x1F\x9D' then
|
||||
return 'application/x-compress'
|
||||
end
|
||||
|
||||
if _1_2 == '\x1F\x8B' then
|
||||
return 'application/x-gzip'
|
||||
end
|
||||
|
||||
if _1_3 == 'BZh' then
|
||||
return 'application/x-bzip2'
|
||||
end
|
||||
|
||||
if _1_6 == '\xFD7zXZ\x00' then
|
||||
return 'application/x-xz'
|
||||
end
|
||||
|
||||
if _1_6 == '7z\xBC\xAF\x27\x1C' then
|
||||
return 'application/x-7z-compressed'
|
||||
end
|
||||
|
||||
if _1_2 == 'MZ' then
|
||||
local pe_header_start = unpack_le(content:sub(61, 64))
|
||||
local signature = content:sub(pe_header_start + 1, pe_header_start + 4)
|
||||
|
||||
if signature == 'PE\x00\x00' then
|
||||
local image_file_header_start = pe_header_start + 5
|
||||
local characteristics = content:sub(image_file_header_start + 18, image_file_header_start + 19)
|
||||
local is_dll = bitwise_and(characteristics, '\x20\x00') == '\x20\x00'
|
||||
|
||||
if is_dll then
|
||||
return 'application/x-msdownload'
|
||||
end
|
||||
|
||||
return 'application/octet-stream'
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function text_tests(content)
|
||||
local lower_content = content:lower()
|
||||
|
||||
if content:find('^%%!PS-Adobe') then
|
||||
return 'application/postscript'
|
||||
end
|
||||
|
||||
if lower_content:find('<?php', 1, true) or content:find('<?=', 1, true) then
|
||||
return 'application/x-httpd-php'
|
||||
end
|
||||
|
||||
if lower_content:find('^%s*<%?xml') then
|
||||
if content:find('<svg') then
|
||||
return 'image/svg+xml'
|
||||
end
|
||||
if lower_content:find('<!doctype html') then
|
||||
return 'application/xhtml+xml'
|
||||
end
|
||||
if content:find('<rss') then
|
||||
return 'application/rss+xml'
|
||||
end
|
||||
return 'application/xml'
|
||||
end
|
||||
|
||||
if lower_content:find('^%s*<html') or lower_content:find('^%s*<!doctype') then
|
||||
return 'text/html'
|
||||
end
|
||||
|
||||
if lower_content:find('^#![/a-z0-9]+ ?python') then
|
||||
return 'application/x-python'
|
||||
end
|
||||
|
||||
if lower_content:find('^#![/a-z0-9]+ ?perl') then
|
||||
return 'application/x-perl'
|
||||
end
|
||||
|
||||
if lower_content:find('^#![/a-z0-9]+ ?ruby') then
|
||||
return 'application/x-ruby'
|
||||
end
|
||||
|
||||
if lower_content:find('^#![/a-z0-9]+ ?php') then
|
||||
return 'application/x-httpd-php'
|
||||
end
|
||||
|
||||
if lower_content:find('^#![/a-z0-9]+ ?bash') then
|
||||
return 'text/x-shellscript'
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local ext_map = {
|
||||
css = 'text/css',
|
||||
csv = 'text/csv',
|
||||
htm = 'text/html',
|
||||
html = 'text/html',
|
||||
xhtml = 'text/html',
|
||||
ics = 'text/calendar',
|
||||
js = 'application/javascript',
|
||||
php = 'application/x-httpd-php',
|
||||
php3 = 'application/x-httpd-php',
|
||||
php4 = 'application/x-httpd-php',
|
||||
php5 = 'application/x-httpd-php',
|
||||
inc = 'application/x-httpd-php',
|
||||
pl = 'application/x-perl',
|
||||
cgi = 'application/x-perl',
|
||||
py = 'application/x-python',
|
||||
rb = 'application/x-ruby',
|
||||
rhtml = 'application/x-ruby',
|
||||
rss = 'application/rss+xml',
|
||||
sh = 'text/x-shellscript',
|
||||
tab = 'text/tab-separated-values',
|
||||
vcf = 'text/x-vcard',
|
||||
xml = 'application/xml'
|
||||
}
|
||||
|
||||
function ext_tests(ext)
|
||||
local mimetype = ext_map[ext]
|
||||
if mimetype then
|
||||
return mimetype
|
||||
end
|
||||
return 'text/plain'
|
||||
end
|
||||
|
||||
|
||||
local _M = {}
|
||||
|
||||
|
||||
function _M.via_path(path, filename)
|
||||
local f, err = io.open(path, 'r')
|
||||
if not f then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local content = f:read(4096)
|
||||
f:close()
|
||||
|
||||
if not filename then
|
||||
filename = basename(path)
|
||||
end
|
||||
|
||||
return _M.via_content(content, filename)
|
||||
end
|
||||
|
||||
|
||||
function _M.via_content(content, filename)
|
||||
local ext = extension(filename)
|
||||
|
||||
-- If there are no low ASCII chars and no easily distinguishable tokens,
|
||||
-- we need to detect by file extension
|
||||
|
||||
local mimetype = nil
|
||||
|
||||
mimetype = binary_tests(content, ext)
|
||||
if mimetype then
|
||||
return mimetype
|
||||
end
|
||||
|
||||
-- Binary-looking files should have been detected so far
|
||||
if content:find('[%z\x01-\x08\x0B\x0C\x0E-\x1F]') then
|
||||
return 'application/octet-stream'
|
||||
end
|
||||
|
||||
mimetype = text_tests(content)
|
||||
if mimetype then
|
||||
return mimetype
|
||||
end
|
||||
|
||||
return ext_tests(ext)
|
||||
end
|
||||
|
||||
return _M
|
@ -0,0 +1,13 @@
|
||||
.downloadthis:focus,
|
||||
.downloadthis:active {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.downloadthis:hover {
|
||||
transition: 0.2s;
|
||||
filter: brightness(0.90);
|
||||
}
|
||||
|
||||
.downloadthis:active {
|
||||
filter: brightness(0.80);
|
||||
}
|
36
_quarto.yml
36
_quarto.yml
@ -41,31 +41,33 @@ website:
|
||||
contents:
|
||||
- href: material/1_mon/rse/rse_basics_slides.qmd
|
||||
text: "📊 1 - RSE"
|
||||
- href: material/1_mon/why_julia/page.qmd
|
||||
- href: "material/1_mon/why_julia/page.qmd"
|
||||
text: "📊 2 - Why Julia"
|
||||
- href: material/1_mon/firststeps/firststeps_handout.qmd
|
||||
- href: "material/1_mon/firststeps/firststeps_handout.qmd"
|
||||
text: "📝 3 - First Steps: Handout"
|
||||
- href: material/1_mon/firststeps/tasks.qmd
|
||||
- href: "material/1_mon/firststeps/tasks.qmd"
|
||||
text: "🛠 3 - First Steps: Exercises"
|
||||
- href: material/1_mon/envs/envs_handout.qmd
|
||||
- href: "material/1_mon/envs/envs_handout.qmd"
|
||||
text: "📝 4 - Envs & Pkgs : Handout"
|
||||
- href: material/1_mon/envs/tasks.qmd
|
||||
- href: "material/1_mon/envs/tasks.qmd"
|
||||
text: "🛠 4 - Envs & Pkgs: Exercises"
|
||||
- section: "Tuesday"
|
||||
contents:
|
||||
- href: material/2_tue/git/intro_slides.md
|
||||
text: "📝 1 - GIT"
|
||||
- href: material/2_tue/unittest/missing.qmd
|
||||
- href: material/2_tue/git/slides.md
|
||||
text: "📝 1 - Advanced Git and Contributing"
|
||||
- href: "material/2_tue/git/tasks.qmd"
|
||||
text: "🛠 1 - Git: Exercises"
|
||||
- href: "material/2_tue/unittest/missing.qmd"
|
||||
text: "📝 2 - Unit Testing"
|
||||
- href: material/2_tue/CI/missing.qmd
|
||||
- href: "material/2_tue/CI/missing.qmd"
|
||||
text: "📝 3 - Continuous Integration"
|
||||
- href: material/2_tue/codereview/slides.qmd
|
||||
text: "📊 4 - Code Review"
|
||||
text: "📝 4 - Code Review"
|
||||
- section: "Wednesday"
|
||||
contents:
|
||||
- href: material/3_wed/docs/handout.qmd
|
||||
text: "📝 1 - Docs: Handout"
|
||||
- href: material/3_wed/docs/tasks.qmd
|
||||
- href: material/3_wed/docs/tasks.qmd"
|
||||
text: "🛠 1 - Docs: Exercises"
|
||||
- href: material/3_wed/vis/handout.qmd
|
||||
text: "📝 2 - Visualizations: Handout"
|
||||
@ -73,15 +75,15 @@ website:
|
||||
text: "🛠 2 - Visualizations: Exercises"
|
||||
- href: material/3_wed/linalg/slides.qmd
|
||||
text: "📝 3 - LinearAlgebra"
|
||||
- href: material/3_wed/regression/missing.jl
|
||||
- href: material/3_wed/regression/MultipleRegressionBasics.qmd
|
||||
text: "📝 4 - Multiple Regression"
|
||||
|
||||
- section: "Thursday"
|
||||
contents:
|
||||
- href: material/4_thu/sim/slides.qmd
|
||||
text: "📊 1 - Simulation"
|
||||
text: "📝 1 - Simulation"
|
||||
- href: material/4_thu/stats/missing.jl
|
||||
text: "📝 2 - Stats"
|
||||
text: "📝 2 - Bootstrapping"
|
||||
- href: material/4_thu/parallel/slides.qmd
|
||||
text: "📝 3 - Parallelization"
|
||||
- section: "Friday"
|
||||
@ -93,11 +95,11 @@ website:
|
||||
background: primary
|
||||
page-footer:
|
||||
background: light
|
||||
left: "CC-By Ehinger, Oesting, Uekerman"
|
||||
left: "CC-By Ehinger, Oesting, Uekerman, DeBruine, Ranocha, Szufel"
|
||||
resources:
|
||||
- CNAME
|
||||
|
||||
|
||||
keep-ipynb: true
|
||||
format:
|
||||
html:
|
||||
email-obfuscation: javascript
|
||||
@ -117,4 +119,4 @@ format:
|
||||
sidebar-width: 400px
|
||||
code-annotations: hover
|
||||
margin-header: |
|
||||

|
||||

|
||||
|
@ -18,6 +18,6 @@ Keep this website ready and have a look at the schedule!
|
||||
|
||||
----
|
||||
|
||||
We wish you all a interesting, safe and fun summerschool. If there are any interpersonal issues (especially regarding [code-of-conduct](https://www.uni-stuttgart.de/en/university/profile/diversity/code-of-conduct/)), please directly contact [Benedikt Ehinger](benedikt.ehinger@vis.uni-stuttgart.de)^[If there are problem with him, please contact **Marco Oesting**]. For organizational issues, please contact TODO
|
||||
We wish you all a interesting, safe and fun summerschool. If there are any interpersonal issues (especially regarding [code-of-conduct](https://www.uni-stuttgart.de/en/university/profile/diversity/code-of-conduct/)), please directly contact [Benedikt Ehinger](benedikt.ehinger@vis.uni-stuttgart.de)^[If there are problem with him, please contact **Marco Oesting**]. For organizational issues, please contact [Sina Schorndorfer](sina.schorndorfer@imsb.uni-stuttgart.de)
|
||||
|
||||
Best, Benedikt, Benjamin, Marco
|
@ -21,10 +21,6 @@ VSCode automatically loads the `Revise.jl` package, which screens all your activ
|
||||
|
||||
## Syntax differences Python/R/MatLab
|
||||
|
||||
### In the beginning there was `nothing`
|
||||
`nothing`- but also `NaN` and also `Missing`.
|
||||
|
||||
Each of those has a specific purpose, but most likely we will only need `a = nothing` and `b = NaN`.
|
||||
|
||||
### Control Structures
|
||||
|
||||
@ -94,6 +90,14 @@ end
|
||||
myfunction(args...;kwargs...) = myotherfunction(newarg,args...;kwargs...)
|
||||
```
|
||||
|
||||
### In the beginning there was `nothing`
|
||||
`nothing`- but also `NaN` and also `Missing`.
|
||||
|
||||
Each of those has a specific purpose, but most likely we will only need `a = nothing` and `b = NaN`.
|
||||
|
||||
Note that `NaN` counts as a Float-Number, whereas nothing & missing does not.
|
||||
|
||||
|
||||
#### Excourse: splatting & slurping
|
||||
|
||||
Think of it as unpacking / collecting something
|
||||
|
@ -1,5 +1,8 @@
|
||||
---
|
||||
format: revealjs
|
||||
format:
|
||||
revealjs:
|
||||
output-file: rse_basics_slides_revealjs.html
|
||||
html: default
|
||||
---
|
||||
|
||||
# Introduction to the Research Software Engineering Summerschol
|
||||
@ -10,6 +13,8 @@ format: revealjs
|
||||
|
||||
Find all slides, all materials, and the schedule
|
||||
|
||||
([link to presentation](rse_basics_slides_revealjs.html))
|
||||
|
||||
## Last minute organization issues
|
||||
|
||||
-
|
||||
@ -20,8 +25,6 @@ Find all slides, all materials, and the schedule
|
||||
|
||||
- Check out the schedule
|
||||
|
||||
- You will learn to use basic Julia
|
||||
|
||||
- In the beginning we will focus on the Research Software Engineering part!
|
||||
|
||||
- Advanced Julia, later this week and by request ;)
|
||||
|
@ -1,102 +0,0 @@
|
||||
# Git Demo
|
||||
|
||||
## Recap of Git basics
|
||||
|
||||
- Expert level poll on git: ask students to estimate their level.
|
||||
- Beginner: I have hardly ever used Git
|
||||
- User: pull, commit, push, status, diff
|
||||
- Developer: fork, branch, merge, checkout
|
||||
- Maintainer: rebase, squash, cherry-pick, bisect
|
||||
- Owner: submodules
|
||||
|
||||

|
||||
|
||||
- `git --help`, `git commit --help`
|
||||
- incomplete statement `git comm`
|
||||
|
||||
- There is a difference between Git and hosting services ([*forges*](https://en.wikipedia.org/wiki/Forge_(software)))
|
||||
- [GitHub](https://github.com/)
|
||||
- [GitLab](https://about.gitlab.com/), open-source, hosted e.g. at [IPVS](https://gitlab-sim.informatik.uni-stuttgart.de)
|
||||
- [Bitbucket](https://bitbucket.org/product/)
|
||||
- [SourceForge](https://sourceforge.net/)
|
||||
- many more
|
||||
- often, more than just hosting, also DevOps
|
||||
|
||||
- Give outlook on remainder of Git chapter: *How I work with Git*, quiz, advanced topics (workflows, rebase, standards), *my neat little Git trick*
|
||||
|
||||
## How I work with Git
|
||||
|
||||
Starting remarks:
|
||||
|
||||
- There is not *the one solution* how to do things with Git. I simply show what I typically use.
|
||||
- Don't use a client if you don't understand the command line `git`
|
||||
|
||||
- (1) Look at GitHub
|
||||
- [preCICE repository](https://github.com/precice/precice)
|
||||
- default branch `develop`
|
||||
- fork -> my fork
|
||||
|
||||
- (2) Working directory:
|
||||
- ZSH shell shows git branches
|
||||
- `git remote -v` (I have upstream, myfork, ...)
|
||||
- mention difference between ssh and https (also see GitHub)
|
||||
- get newest changes `git pull upstream develop`
|
||||
- `git log` -> I use special format, see `~/.gitconfig`,
|
||||
- check log on GitHub; explain short hash
|
||||
- `git branch`
|
||||
- `git branch add-demo-feature`
|
||||
- `git checkout add-demo-feature`
|
||||
|
||||
- (3) First commit
|
||||
- `git status` -> always tells you what you can do
|
||||
- `vi src/action/Action.hpp` -> add `#include "MagicHeader.hpp"`
|
||||
- `git diff`, `git diff src/com/Action.hpp`, `git diff --color-words`
|
||||
- `git status`, `git add`, `git status`
|
||||
- `git commit` -> "Include MagicHeader in Action.hpp"
|
||||
- `git status`, `git log`, `git log -p`, `git show`
|
||||
|
||||
- (4) Change or revert things
|
||||
- I forgot to add sth: `git reset --soft HEAD~1`, `git status`
|
||||
- `git diff`, `git diff HEAD` because already staged
|
||||
- `git log`
|
||||
- `git commit`
|
||||
- actually all that is nonsense: `git reset --hard HEAD~1`
|
||||
- modify again, all nonsense before committing: `git checkout src/action/Action.hpp`
|
||||
|
||||
- (5) Stash
|
||||
- while working on unfinished feature, I need to change / test this other thing quickly, too lazy for commits / branches
|
||||
- `git stash`
|
||||
- `git stash pop`
|
||||
|
||||
- (6) Create PR
|
||||
- create commit again
|
||||
- preview what will be in PR: `git diff develop..add-demo-feature`
|
||||
- `git push -u myfork add-demo-feature` -> copy link
|
||||
- explain PR template
|
||||
- explain target branch
|
||||
- explain "Allow edits by maintainers"
|
||||
- cancel
|
||||
- my fork -> branches -> delete
|
||||
|
||||
- (7) Check out someone else's work
|
||||
- have a look at an existing PR, look at all tabs, show suggestion feature
|
||||
- but sometimes we want to really build and try sth out ...
|
||||
- `git remote -v`
|
||||
- `git remote add alex git@github.com:ajaust/precice.git` if I don't have remote already (or somebody else)
|
||||
- `git fetch alex`
|
||||
- `git checkout -t alex/[branch-name]`
|
||||
- I could now also push to `ajaust`'s remote
|
||||
|
||||
## Further reading
|
||||
|
||||
### Quick things
|
||||
|
||||
- [Video: Git in 15 minutes: basics, branching, no remote](https://www.youtube.com/watch?v=USjZcfj8yxE)
|
||||
- [The GitHub Blog: Commits are snapshots, not diffs](https://github.blog/2020-12-17-commits-are-snapshots-not-diffs/)
|
||||
- Chapters [6](https://merely-useful.tech/py-rse/git-cmdline.html) and [7](https://merely-useful.tech/py-rse/git-advanced.html) of Research Software Engineering with Python
|
||||
- [Podcast All Things Git: History of VC](https://www.allthingsgit.com/episodes/the_history_of_vc_with_eric_sink.html)
|
||||
- [git purr](https://girliemac.com/blog/2017/12/26/git-purr/)
|
||||
|
||||
### References
|
||||
|
||||
- [Official documentation](http://git-scm.com/doc)
|
@ -1,113 +0,0 @@
|
||||
---
|
||||
type: slide
|
||||
slideOptions:
|
||||
transition: slide
|
||||
width: 1400
|
||||
height: 900
|
||||
margin: 0.1
|
||||
---
|
||||
|
||||
<style>
|
||||
.reveal strong {
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
.reveal p {
|
||||
text-align: left;
|
||||
}
|
||||
.reveal section h1 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal section h2 {
|
||||
color: orange;
|
||||
}
|
||||
</style>
|
||||
|
||||
# Introduction to version control
|
||||
|
||||
---
|
||||
|
||||
## Learning goals of section
|
||||
|
||||
- Refresh and organize students' existing knowledge on Git (learn how to learn more).
|
||||
- Students can explain difference between merge and rebase and when to use what.
|
||||
- How to use Git workflows to organize research software development in a team.
|
||||
- Get to know a few useful GitHub/GitLab standards and a few helpful tools.
|
||||
|
||||
---
|
||||
|
||||
## Why do we need version control?
|
||||
|
||||
Version control ...
|
||||
|
||||
- tracks changes to files and helps people share those changes with each other.
|
||||
- Could also be done via email / Google Docs / ..., but not as accurately and efficiently
|
||||
- was originally developed for software development, but today cornerstone of *reproducible research*
|
||||
|
||||
> "If you can't git diff a file format, it's broken."
|
||||
|
||||
---
|
||||
|
||||
## How does version control work?
|
||||
|
||||
- *master* (or *main*) copy of code in repository, can't edit directly
|
||||
- Instead: check out a working copy of code, edit, commit changes back
|
||||
- Repository records complete revision history
|
||||
- You can go back in time
|
||||
- It's clear who did what when
|
||||
|
||||
---
|
||||
|
||||
## The alternative: A story told in file names
|
||||
|
||||
<img src="http://phdcomics.com/comics/archive/phd052810s.gif" width=60% style="margin-left:auto; margin-right:auto">
|
||||
|
||||
[http://phdcomics.com/comics/archive/phd052810s.gif](http://phdcomics.com/comics/archive/phd052810s.gif)
|
||||
|
||||
---
|
||||
|
||||
## A very short history of version control I
|
||||
|
||||
The old centralized variants:
|
||||
|
||||
- 1982: RCS (Revision Control System), operates on single files
|
||||
- 1986 (release in 1990): CVS (Concurrent Versions System), front end of RCS, operates on whole projects
|
||||
- 1994: VSS (Microsoft Visual SourceSafe)
|
||||
- 2000: SVN (Apache Subversion), mostly compatible successor of CVS, *still used today*
|
||||
|
||||
---
|
||||
|
||||
## A very short history of version control II
|
||||
|
||||
Distributed version control:
|
||||
|
||||
- Besides remote master version, also local copy of repository
|
||||
- More memory required, but much better performance
|
||||
- For a long time: highly fragmented market
|
||||
- 2000: BitKeeper (originally proprietary software)
|
||||
- 2005: Mercurial
|
||||
- 2005: Git
|
||||
- A few more
|
||||
|
||||
Learn more: [Podcast All Things Git: History of VC](https://www.allthingsgit.com/episodes/the_history_of_vc_with_eric_sink.html)
|
||||
|
||||
---
|
||||
|
||||
## The only standard today: Git
|
||||
|
||||
No longer a fragmented market, there is nearly only Git today:
|
||||
|
||||
- [Stackoverflow developer survey 2021](https://insights.stackoverflow.com/survey/2021#technology-most-popular-technologies):
|
||||
> "Over 90% of respondents use Git, suggesting that it is a fundamental tool to being a developer."
|
||||
- All software project candidates for *contribution challenge* use Git
|
||||
- Is this good or bad?
|
||||
|
||||
---
|
||||
|
||||
## More facts on Git
|
||||
|
||||
Git itself is open-source: GPL license
|
||||
|
||||
- source on [GitHub](https://github.com/git/git), contributions are a bit more complicated than a simple PR
|
||||
- written mainly in C
|
||||
- started by Linus Torvalds, core maintainer since later 2005: Junio Hamano
|
@ -1,148 +0,0 @@
|
||||
---
|
||||
type: slide
|
||||
slideOptions:
|
||||
transition: slide
|
||||
width: 1400
|
||||
height: 900
|
||||
margin: 0.1
|
||||
---
|
||||
|
||||
<style>
|
||||
.reveal strong {
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
.reveal p {
|
||||
text-align: left;
|
||||
}
|
||||
.reveal section h1 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal section h2 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal code {
|
||||
font-family: 'Ubuntu Mono';
|
||||
color: orange;
|
||||
}
|
||||
.reveal section img {
|
||||
background:none;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
}
|
||||
</style>
|
||||
|
||||
# Merge vs. Rebase
|
||||
|
||||
---
|
||||
|
||||
## Linear History
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Simulation-Software-Engineering/Lecture-Material/main/01_version_control/figs/history_linear/fig.png" width=60%; style="margin-left:auto; margin-right:auto; padding-top: 25px; padding-bottom: 25px">
|
||||
|
||||
- Commits are snapshots + pointer to parent, not diffs
|
||||
- But for linear history, this makes no difference
|
||||
- Each normal commit has one parent commit
|
||||
- `c05f017^` <-- `c05f017`
|
||||
- `A` = `B^` <-- `B`
|
||||
- (`^` is the same as `~1`)
|
||||
- Pointer to parent commit goes into hash
|
||||
- `git show` gives diff of commit to parent
|
||||
|
||||
---
|
||||
|
||||
## Merge Commits
|
||||
|
||||
- `git checkout main && git merge feature`
|
||||
<img src="https://raw.githubusercontent.com/Simulation-Software-Engineering/Lecture-Material/main/01_version_control/figs/history_merge/fig.png" width=70%; style="margin-left:auto; margin-right:auto; padding-top: 25px; padding-bottom: 25px" >
|
||||
- A merge commit (normally) has two parent commits `M^1` and `M^2` (don't confuse `^2` with `~2`)
|
||||
- Can't show unique diff
|
||||
- First parent relative to the branch you are on (`M^1` = `C`, `M^2` = `E`)
|
||||
- `git show`
|
||||
- `git show`: *"combined diff"*
|
||||
- GitHub: `git show --first-parent`
|
||||
- `git show -m`: separate diff to all parents
|
||||
|
||||
---
|
||||
|
||||
## Why is a Linear History Important?
|
||||
|
||||
We use here:
|
||||
|
||||
> Linear history := no merge commits
|
||||
|
||||
- Merge commits are hard to understand per se.
|
||||
- A merge takes all commits from `feature` to `main` (on `git log`). --> Hard to understand
|
||||
- Developers often follow projects by reading commits (reading the diffs). --> Harder to read (where happened what)
|
||||
- Tracing bugs easier with linear history (see `git bisect`)
|
||||
- Example: We know a bug was introduced between `v1.3` and `v1.4`.
|
||||
|
||||
---
|
||||
|
||||
## How to get a Linear History?
|
||||
|
||||
- Real conflicts are very rare in real projects, most merge commits are false positives (not conflicts) and should be avoided.
|
||||
- If there are no changes on `main`, `git merge` does a *"fast-forward"* merge (no merge commit).
|
||||
- If there are changes on `main`, rebase `feature` branch.
|
||||
|
||||
---
|
||||
|
||||
## Rebase
|
||||
|
||||
- `git checkout feature && git rebase main`
|
||||
<img src="https://raw.githubusercontent.com/Simulation-Software-Engineering/Lecture-Material/main/01_version_control/figs/history_rebase/fig.png" width=90%; style="margin-left:auto; margin-right:auto; padding-top: 25px; padding-bottom: 25px">
|
||||
- States of issues change (and new parents) --> history is rewritten
|
||||
- If `feature` is already on remote, it needs a force push `git push --force myfork feature` (or `--force-with-lease`).
|
||||
- Be careful: Only use rebase if **only you** work on a branch (a local branch or a branch on your fork).
|
||||
- For local branches very helpful: `git pull --rebase` (fetch & rebase)
|
||||
|
||||
---
|
||||
|
||||
## GitHub PR Merge Variants
|
||||
|
||||
- GitHub offers three ways to merge a non-conflicting (no changes in same files) PR:
|
||||
- Create a merge commit
|
||||
- Squash and merge
|
||||
- Rebase and merge
|
||||
- Look at a PR together, e.g. [PR 1432 from preCICE](https://github.com/precice/precice/pull/1432) (will be closed eventually)
|
||||
|
||||
> What do the options do?
|
||||
|
||||
---
|
||||
|
||||
## Squash and Merge
|
||||
|
||||
- ... squashes all commits into one
|
||||
- Often, single commits of feature branch are important while developing the feature,
|
||||
- ... but not when the feature is merged
|
||||
- Works well for small feature PRs
|
||||
- ... also does a rebase (interactively, `git rebase -i`)
|
||||
|
||||
---
|
||||
|
||||
## Conflicts
|
||||
|
||||
> But what if there is a conflict?
|
||||
|
||||
- Resolve by rebasing `feature` branch (recommended)
|
||||
- Or resolve by merging `main` into `feature`
|
||||
|
||||
---
|
||||
|
||||
## Summary and Final Remarks
|
||||
|
||||
- Try to keep a linear history with rebasing whenever reasonable
|
||||
- Don't use rebase on a public/shared branch during development
|
||||
- Squash before merging if reasonable
|
||||
- Delete `feature` branch after merging
|
||||
- Local view: `git log --graph`
|
||||
- Remote view on GitHub, e.g. [for preCICE](https://github.com/precice/precice/network)
|
||||
|
||||
---
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Bitbucket docs: "Merging vs. Rebasing"](https://www.atlassian.com/git/tutorials/merging-vs-rebasing)
|
||||
- [Hackernoon: "What's the diff?"](https://hackernoon.com/git-merge-vs-rebase-whats-the-diff-76413c117333)
|
||||
- [GitHub Blog: "Commits are snapshots, not diffs"](https://github.blog/2020-12-17-commits-are-snapshots-not-diffs/)
|
||||
- [Stack Overflow: "Git show of a merge commit"](https://stackoverflow.com/questions/40986518/git-show-of-a-merge-commit?)
|
516
material/2_tue/git/slides.md
Normal file
516
material/2_tue/git/slides.md
Normal file
@ -0,0 +1,516 @@
|
||||
---
|
||||
type: slide
|
||||
slideOptions:
|
||||
transition: slide
|
||||
width: 1400
|
||||
height: 900
|
||||
margin: 0.1
|
||||
---
|
||||
|
||||
<style>
|
||||
.reveal strong {
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
.reveal p {
|
||||
text-align: left;
|
||||
}
|
||||
.reveal section h1 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal section h2 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal code {
|
||||
font-family: 'Ubuntu Mono';
|
||||
color: orange;
|
||||
}
|
||||
.reveal section img {
|
||||
background:none;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
}
|
||||
</style>
|
||||
|
||||
## Learning Goals of the Git Lecture
|
||||
|
||||
- Refresh and organize students' existing knowledge on Git (learn how to learn more).
|
||||
- Students can explain difference between merge and rebase and when to use what.
|
||||
- How to use Git workflows to organize research software development in a team.
|
||||
- Get to know a few useful GitHub/GitLab standards and a few helpful tools.
|
||||
- Get to know a few rules on good commit messages.
|
||||
|
||||
Material is taken and modified from the [SSE lecture](https://github.com/Simulation-Software-Engineering/Lecture-Material), which builds partly on the [py-rse book](https://merely-useful.tech/py-rse).
|
||||
|
||||
---
|
||||
|
||||
# 1. Introduction to Version Control
|
||||
|
||||
---
|
||||
|
||||
## Why Do We Need Version Control?
|
||||
|
||||
Version control ...
|
||||
|
||||
- tracks changes to files and helps people share those changes with each other.
|
||||
- Could also be done via email / Google Docs / ..., but not as accurately and efficiently
|
||||
- was originally developed for software development, but today cornerstone of *reproducible research*
|
||||
|
||||
> "If you can't git diff a file format, it's broken."
|
||||
|
||||
---
|
||||
|
||||
## How Does Version Control Work?
|
||||
|
||||
- *master* (or *main*) copy of code in repository, can't edit directly
|
||||
- Instead: check out a working copy of code, edit, commit changes back
|
||||
- Repository records complete revision history
|
||||
- You can go back in time
|
||||
- It's clear who did what when
|
||||
|
||||
---
|
||||
|
||||
## The Alternative: A Story Told in File Names
|
||||
|
||||
<img src="http://phdcomics.com/comics/archive/phd052810s.gif" width=60% style="margin-left:auto; margin-right:auto">
|
||||
|
||||
[http://phdcomics.com/comics/archive/phd052810s.gif](http://phdcomics.com/comics/archive/phd052810s.gif)
|
||||
|
||||
---
|
||||
|
||||
## A Very Short History of Version Control I
|
||||
|
||||
The old centralized variants:
|
||||
|
||||
- 1982: RCS (Revision Control System), operates on single files
|
||||
- 1986 (release in 1990): CVS (Concurrent Versions System), front end of RCS, operates on whole projects
|
||||
- 1994: VSS (Microsoft Visual SourceSafe)
|
||||
- 2000: SVN (Apache Subversion), mostly compatible successor of CVS, *still used today*
|
||||
|
||||
---
|
||||
|
||||
## A Very Short History of Version Control II
|
||||
|
||||
Distributed version control:
|
||||
|
||||
- Besides remote master version, also local copy of repository
|
||||
- More memory required, but much better performance
|
||||
- For a long time: highly fragmented market
|
||||
- 2000: BitKeeper (originally proprietary software)
|
||||
- 2005: Mercurial
|
||||
- 2005: Git
|
||||
- A few more
|
||||
|
||||
Learn more: [Podcast All Things Git: History of VC](https://www.allthingsgit.com/episodes/the_history_of_vc_with_eric_sink.html)
|
||||
|
||||
---
|
||||
|
||||
## The Only Standard Today: Git
|
||||
|
||||
No longer a fragmented market, there is nearly only Git today:
|
||||
|
||||
- [Stackoverflow developer survey 2021](https://insights.stackoverflow.com/survey/2021#technology-most-popular-technologies):
|
||||
> "Over 90% of respondents use Git, suggesting that it is a fundamental tool to being a developer."
|
||||
- Is this good or bad?
|
||||
|
||||
---
|
||||
|
||||
## More Facts on Git
|
||||
|
||||
- Git itself is open-source: GPL license
|
||||
- Source code on [GitHub](https://github.com/git/git), contributions are a bit more complicated than a simple PR
|
||||
- Written mainly in C
|
||||
- Started by Linus Torvalds, core maintainer since later 2005: Junio Hamano
|
||||
- **Git** (the version control software) vs. **git** (the command line interface)
|
||||
|
||||
---
|
||||
|
||||
## Forges
|
||||
|
||||
There is a difference between Git and hosting services ([*forges*](https://en.wikipedia.org/wiki/Forge_(software))):
|
||||
|
||||
- [GitHub](https://github.com/)
|
||||
- [GitLab](https://about.gitlab.com/), open-source, hosted e.g. at [IPVS](https://gitlab-sim.informatik.uni-stuttgart.de)
|
||||
- [Bitbucket](https://bitbucket.org/product/)
|
||||
- [SourceForge](https://sourceforge.net/)
|
||||
- many more
|
||||
- often, more than just hosting, also DevOps
|
||||
|
||||
---
|
||||
|
||||
# 2. Recap of Git Basics
|
||||
|
||||
---
|
||||
|
||||
## Expert level poll
|
||||
|
||||
Which level do you have?
|
||||
|
||||
- **Beginner**: hardly ever used Git
|
||||
- **User**: pull, commit, push, status, diff
|
||||
- **Developer**: fork, branch, merge, checkout
|
||||
- **Maintainer**: rebase, squash, cherry-pick, bisect
|
||||
- **Owner**: submodules
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
<img src="https://merely-useful.tech/py-rse/figures/git-cmdline/git-remote.png" width=60% style="margin-left:auto; margin-right:auto">
|
||||
|
||||
[Git overview picture from py-rse](https://merely-useful.tech/py-rse/figures/git-cmdline/git-remote.png)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Demo
|
||||
|
||||
- `git --help`, `git commit --help`
|
||||
- incomplete statement `git comm`
|
||||
- There is not *the one solution* how to do things with Git. I simply show what I typically use.
|
||||
- Don't use a client if you don't understand the command line `git`
|
||||
|
||||
- (1) Look at GitHub
|
||||
- [preCICE repository](https://github.com/precice/precice)
|
||||
- default branch `develop`
|
||||
- fork -> my fork
|
||||
|
||||
- (2) Working directory:
|
||||
- ZSH shell shows git branches
|
||||
- `git remote -v` (I have upstream, myfork, ...)
|
||||
- mention difference between ssh and https (also see GitHub)
|
||||
- get newest changes `git pull upstream develop`
|
||||
- `git log` -> I use special format, see `~/.gitconfig`,
|
||||
- check log on GitHub; explain short hash
|
||||
- `git branch`
|
||||
- `git branch add-demo-feature`
|
||||
- `git checkout add-demo-feature`
|
||||
|
||||
- (3) First commit
|
||||
- `git status` -> always tells you what you can do
|
||||
- `vi src/action/Action.hpp` -> add `#include "MagicHeader.hpp"`
|
||||
- `git diff`, `git diff src/com/Action.hpp`, `git diff --color-words`
|
||||
- `git status`, `git add`, `git status`
|
||||
- `git commit` -> "Include MagicHeader in Action.hpp"
|
||||
- `git status`, `git log`, `git log -p`, `git show`
|
||||
|
||||
- (4) Change or revert things
|
||||
- I forgot to add sth: `git reset --soft HEAD~1`, `git status`
|
||||
- `git diff`, `git diff HEAD` because already staged
|
||||
- `git log`
|
||||
- `git commit`
|
||||
- actually all that is nonsense: `git reset --hard HEAD~1`
|
||||
- modify again, all nonsense before committing: `git checkout src/action/Action.hpp`
|
||||
|
||||
- (5) Stash
|
||||
- while working on unfinished feature, I need to change / test this other thing quickly, too lazy for commits / branches
|
||||
- `git stash`
|
||||
- `git stash pop`
|
||||
|
||||
- (6) Create PR
|
||||
- create commit again
|
||||
- preview what will be in PR: `git diff develop..add-demo-feature`
|
||||
- `git push -u myfork add-demo-feature` -> copy link
|
||||
- explain PR template
|
||||
- explain target branch
|
||||
- explain "Allow edits by maintainers"
|
||||
- cancel
|
||||
- my fork -> branches -> delete
|
||||
|
||||
- (7) Check out someone else's work
|
||||
- have a look at an existing PR, look at all tabs, show suggestion feature
|
||||
- but sometimes we want to really build and try sth out ...
|
||||
- `git remote -v`
|
||||
- `git remote add alex git@github.com:ajaust/precice.git` if I don't have remote already (or somebody else)
|
||||
- `git fetch alex`
|
||||
- `git checkout -t alex/[branch-name]`
|
||||
- I could now also push to `ajaust`'s remote
|
||||
|
||||
---
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Official documentation](http://git-scm.com/doc)
|
||||
- [Video: Git in 15 minutes: basics, branching, no remote](https://www.youtube.com/watch?v=USjZcfj8yxE)
|
||||
- [The GitHub Blog: Commits are snapshots, not diffs](https://github.blog/2020-12-17-commits-are-snapshots-not-diffs/)
|
||||
- Chapters [6](https://merely-useful.tech/py-rse/git-cmdline.html) and [7](https://merely-useful.tech/py-rse/git-advanced.html) of Research Software Engineering with Python
|
||||
- [Podcast All Things Git: History of VC](https://www.allthingsgit.com/episodes/the_history_of_vc_with_eric_sink.html)
|
||||
- [git purr](https://girliemac.com/blog/2017/12/26/git-purr/)
|
||||
|
||||
---
|
||||
|
||||
# 3. Merge vs. Rebase
|
||||
|
||||
---
|
||||
|
||||
## Linear History
|
||||
|
||||
<img src="https://raw.githubusercontent.com/s-ccs/summerschool_simtech_2023/main/material/2_tue/git/figs/history_linear/fig.png" width=60%; style="margin-left:auto; margin-right:auto; padding-top: 25px; padding-bottom: 25px">
|
||||
|
||||
- Commits are snapshots + pointer to parent, not diffs
|
||||
- But for linear history, this makes no difference
|
||||
- Each normal commit has one parent commit
|
||||
- `c05f017^` <-- `c05f017`
|
||||
- `A` = `B^` <-- `B`
|
||||
- (`^` is the same as `~1`)
|
||||
- Pointer to parent commit goes into hash
|
||||
- `git show` gives diff of commit to parent
|
||||
|
||||
---
|
||||
|
||||
## Merge Commits
|
||||
|
||||
- `git checkout main && git merge feature`
|
||||
<img src="https://raw.githubusercontent.com/s-ccs/summerschool_simtech_2023/main/material/2_tue/git/figs/history_merge/fig.png" width=70%; style="margin-left:auto; margin-right:auto; padding-top: 25px; padding-bottom: 25px" >
|
||||
- A merge commit (normally) has two parent commits `M^1` and `M^2` (don't confuse `^2` with `~2`)
|
||||
- Can't show unique diff
|
||||
- First parent relative to the branch you are on (`M^1` = `C`, `M^2` = `E`)
|
||||
- `git show`
|
||||
- `git show`: *"combined diff"*
|
||||
- GitHub: `git show --first-parent`
|
||||
- `git show -m`: separate diff to all parents
|
||||
|
||||
---
|
||||
|
||||
## Why is a Linear History Important?
|
||||
|
||||
We use here:
|
||||
|
||||
> Linear history := no merge commits
|
||||
|
||||
- Merge commits are hard to understand per se.
|
||||
- A merge takes all commits from `feature` to `main` (on `git log`). --> Hard to understand
|
||||
- Developers often follow projects by reading commits (reading the diffs). --> Harder to read (where happened what)
|
||||
- Tracing bugs easier with linear history (see `git bisect`)
|
||||
- Example: We know a bug was introduced between `v1.3` and `v1.4`.
|
||||
|
||||
---
|
||||
|
||||
## How to get a Linear History?
|
||||
|
||||
- Real conflicts are very rare in real projects, most merge commits are false positives (not conflicts) and should be avoided.
|
||||
- If there are no changes on `main`, `git merge` does a *"fast-forward"* merge (no merge commit).
|
||||
- If there are changes on `main`, rebase `feature` branch.
|
||||
|
||||
---
|
||||
|
||||
## Rebase
|
||||
|
||||
- `git checkout feature && git rebase main`
|
||||
<img src="https://raw.githubusercontent.com/s-ccs/summerschool_simtech_2023/main/material/2_tue/git/figs/history_rebase/fig.png" width=90%; style="margin-left:auto; margin-right:auto; padding-top: 25px; padding-bottom: 25px">
|
||||
- States of issues change (and new parents) --> history is rewritten
|
||||
- If `feature` is already on remote, it needs a force push `git push --force myfork feature` (or `--force-with-lease`).
|
||||
- Be careful: Only use rebase if **only you** work on a branch (a local branch or a branch on your fork).
|
||||
- For local branches very helpful: `git pull --rebase` (fetch & rebase)
|
||||
|
||||
---
|
||||
|
||||
## GitHub PR Merge Variants
|
||||
|
||||
- GitHub offers three ways to merge a non-conflicting (no changes in same files) PR:
|
||||
- Create a merge commit
|
||||
- Squash and merge
|
||||
- Rebase and merge
|
||||
- Look at a PR together, e.g. [PR 1432 from preCICE](https://github.com/precice/precice/pull/1824) (will be closed eventually)
|
||||
|
||||
> What do the options do?
|
||||
|
||||
---
|
||||
|
||||
## Squash and Merge
|
||||
|
||||
- ... squashes all commits into one
|
||||
- Often, single commits of feature branch are important while developing the feature,
|
||||
- ... but not when the feature is merged
|
||||
- Works well for small feature PRs
|
||||
- ... also does a rebase (interactively, `git rebase -i`)
|
||||
|
||||
---
|
||||
|
||||
## Conflicts
|
||||
|
||||
> But what if there is a conflict?
|
||||
|
||||
- Resolve by rebasing `feature` branch (recommended)
|
||||
- Or resolve by merging `main` into `feature`
|
||||
|
||||
---
|
||||
|
||||
## Summary and Remarks
|
||||
|
||||
- Try to keep a linear history with rebasing whenever reasonable
|
||||
- Don't use rebase on a public/shared branch during development
|
||||
- Squash before merging if reasonable
|
||||
- Delete `feature` branch after merging
|
||||
- Local view: `git log --graph`
|
||||
- Remote view on GitHub, e.g. [for preCICE](https://github.com/precice/precice/network)
|
||||
|
||||
---
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Bitbucket docs: "Merging vs. Rebasing"](https://www.atlassian.com/git/tutorials/merging-vs-rebasing)
|
||||
- [Hackernoon: "What's the diff?"](https://hackernoon.com/git-merge-vs-rebase-whats-the-diff-76413c117333)
|
||||
- [GitHub Blog: "Commits are snapshots, not diffs"](https://github.blog/2020-12-17-commits-are-snapshots-not-diffs/)
|
||||
- [Stack Overflow: "Git show of a merge commit"](https://stackoverflow.com/questions/40986518/git-show-of-a-merge-commit?)
|
||||
|
||||
---
|
||||
|
||||
# 4. Working in Teams / Git Workflows
|
||||
|
||||
---
|
||||
|
||||
## Why Workflows?
|
||||
|
||||
- Git offers a lot of flexibility in managing changes.
|
||||
- When working in a team, some agreements need to be made however (especially on how to work with branches).
|
||||
|
||||
---
|
||||
|
||||
## Which Workflow?
|
||||
|
||||
- There are standard solutions.
|
||||
- It depends on the size of the team.
|
||||
- Workflow should enhance effectiveness of team, not be a burden that limits productivity.
|
||||
|
||||
---
|
||||
|
||||
## Centralized Workflow
|
||||
|
||||
- Only one branch: the `main` branch
|
||||
- Keep your changes in local commits till some feature is ready
|
||||
- If ready, directly push to `main`; no PRs, no reviews
|
||||
- Conflicts: fix locally (push not allowed anyway), use `git pull --rebase`
|
||||
- **Good for**: small teams, small projects, projects that are anyway reviewed over and over again
|
||||
- Example: LaTeX papers
|
||||
- Put each section in separate file
|
||||
- Put each sentence in separate line
|
||||
|
||||
---
|
||||
|
||||
## Feature Branch Workflow
|
||||
|
||||
- Each feature (or bugfix) in separate branch
|
||||
- Push feature branch to remote, use descriptive name
|
||||
- e.g. issue number in name if each branch closes one issue
|
||||
- `main` should never contain broken code
|
||||
- Protect direct push to `main`
|
||||
- PR (or MR) with review to merge from feature branch to `main`
|
||||
- Rebase feature branch on `main` if necessary
|
||||
- Delete remote branch once merged and no longer needed (one click on GitHub after merge)
|
||||
- **Good for**: small teams, small projects, prototyping, websites (continuous deployment), documentation
|
||||
- Aka. [trunk-based development](https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development) or [GitHub flow](https://guides.github.com/introduction/flow/)
|
||||
|
||||
---
|
||||
|
||||
## Gitflow
|
||||
|
||||
- [Visualization by Vincent Driessen](https://nvie.com/img/git-model@2x.png), from [original blog post in 2010](https://nvie.com/posts/a-successful-git-branching-model/)
|
||||
- `main` and `develop`
|
||||
- `main` contains releases as tags
|
||||
- `develop` contains latest features
|
||||
- Feature branches created of `develop`, PRs back to `develop`
|
||||
- Protect `main` and (possibly) `develop` from direct pushes
|
||||
- Dedicated release branches (e.g., `v1.0`) created of `develop`
|
||||
- Tested, fixed, merged to `main`
|
||||
- Afterwards, tagged, merged back to `develop`
|
||||
- Hotfix branches directly of and to `main`
|
||||
- **Good for**: software with users, larger teams
|
||||
- There is a tool `git-flow`, a wrapper around `git`, e.g. `git flow init` ... but not really necessary IMHO
|
||||
|
||||
---
|
||||
|
||||
## Forking Workflow
|
||||
|
||||
- Gitflow + feature branches on other forks
|
||||
- More control over access rights, distinguish between maintainers and external contributors
|
||||
- Should maintainers also use branches on their forks?
|
||||
- Makes overview of branches easier
|
||||
- Distinguishes between prototype branches (on fork, no PR), serious enhancements (on fork with PR), joint enhancements (on upstream)
|
||||
- **Good for**: open-source projects with external contributions (used more or less in preCICE)
|
||||
|
||||
---
|
||||
|
||||
## Do Small PRs
|
||||
|
||||
- For all workflows, it is better to do small PRs
|
||||
- Easier to review
|
||||
- Faster to merge --> fewer conflicts
|
||||
- Easier to squash
|
||||
|
||||
---
|
||||
|
||||
## Quick Reads
|
||||
|
||||
- [Atlassian docs on workflows](https://www.atlassian.com/git/tutorials/comparing-workflows)
|
||||
- [Original gitflow blog post](https://nvie.com/posts/a-successful-git-branching-model/)
|
||||
- [Trunk-based development](https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development)
|
||||
- [GitHub flow](https://guides.github.com/introduction/flow/)
|
||||
- [How to keep pull requests manageable](https://gist.github.com/sktse/569cb192ce1518f83db58567591e3205)
|
||||
|
||||
---
|
||||
|
||||
# 5. GitHub / GitLab Standards
|
||||
|
||||
---
|
||||
|
||||
## What Do We Mean With Standards?
|
||||
|
||||
- GitHub uses standards or conventions.
|
||||
- Certain files or names trigger certain behavior automatically.
|
||||
- Many are supported by most forges.
|
||||
- **This is good.**
|
||||
- Everybody should know them.
|
||||
|
||||
---
|
||||
|
||||
## Special Files
|
||||
|
||||
Certain files lead to special formatting (normally directly at root of repo):
|
||||
|
||||
- `README.md`
|
||||
- ... contains meta information / overview / first steps of software.
|
||||
- ... gets rendered on landing page (and in every folder).
|
||||
- `LICENSE`
|
||||
- ... contains software license.
|
||||
- ... gets rendered on right sidebar, when clicking on license, and on repo preview.
|
||||
- `CONTRIBUTING.md`
|
||||
- ... contains guidelines for contributing.
|
||||
- First-time contributors see banner.
|
||||
- `CODE_OF_CONDUCT.md`
|
||||
- ... contains code of conduct.
|
||||
- ... gets rendered on right sidebar.
|
||||
|
||||
---
|
||||
|
||||
## Issues and PRs
|
||||
|
||||
- Templates for description in `.github` folder
|
||||
- `closes #34` (or several other keywords: `fixes`, `resolves`) in commit message or PR description will close issue 34 when merged.
|
||||
- `help wanted` label gets rendered on repo preview (e.g. *"3 issues need help"*).
|
||||
|
||||
---
|
||||
|
||||
# 6. Commit Messages
|
||||
|
||||
---
|
||||
|
||||
## Commit Messages (1/2)
|
||||
|
||||
- Consistent
|
||||
- Descriptive and concise (such that complete history becomes skimmable)
|
||||
- Explain the "why" (the "how" is covered in the diff)
|
||||
|
||||
---
|
||||
|
||||
## Commit Messages (2/2)
|
||||
|
||||
[The seven rules of a great Git commit message](https://chris.beams.io/git-commit/):
|
||||
|
||||
- Separate subject from body with a blank line.
|
||||
- Limit the subject line to 50 characters.
|
||||
- Capitalize the subject line.
|
||||
- Do not end the subject line with a period.
|
||||
- Use the imperative mood in the subject line.
|
||||
- Wrap the body at 72 characters.
|
||||
- Use the body to explain what and why vs. how.
|
@ -1,72 +0,0 @@
|
||||
---
|
||||
type: slide
|
||||
slideOptions:
|
||||
transition: slide
|
||||
width: 1400
|
||||
height: 900
|
||||
margin: 0.1
|
||||
---
|
||||
|
||||
<style>
|
||||
.reveal strong {
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
.reveal p {
|
||||
text-align: left;
|
||||
}
|
||||
.reveal section h1 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal section h2 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal code {
|
||||
font-family: 'Ubuntu Mono';
|
||||
color: orange;
|
||||
}
|
||||
.reveal section img {
|
||||
background:none;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
}
|
||||
</style>
|
||||
|
||||
# GitHub / GitLab Standards
|
||||
|
||||
---
|
||||
|
||||
## What do we mean with Standards?
|
||||
|
||||
- GitHub uses standards or conventions.
|
||||
- Certain files or names trigger certain behavior automatically.
|
||||
- Many are supported by most forges.
|
||||
- **This is good.**
|
||||
- Everybody should know them.
|
||||
|
||||
---
|
||||
|
||||
## Special Files
|
||||
|
||||
Certain files lead to special formatting (normally directly at root of repo):
|
||||
|
||||
- `README.md`
|
||||
- ... contains meta information / overview / first steps of software.
|
||||
- ... gets rendered on landing page (and in every folder).
|
||||
- `LICENSE`
|
||||
- ... contains software license.
|
||||
- ... gets rendered on right sidebar, when clicking on license, and on repo preview.
|
||||
- `CONTRIBUTING.md`
|
||||
- ... contains guidelines for contributing.
|
||||
- First-time contributors see banner.
|
||||
- `CODE_OF_CONDUCT.md`
|
||||
- ... contains code of conduct.
|
||||
- ... gets rendered on right sidebar.
|
||||
|
||||
---
|
||||
|
||||
## Issues and PRs
|
||||
|
||||
- Templates for description in `.github` folder
|
||||
- `closes #34` (or several other keywords: `fixes`, `resolves`) in commit message or PR description will close issue 34 when merged.
|
||||
- `help wanted` label gets rendered on repo preview (e.g. *"3 issues need help"*).
|
8
material/2_tue/git/tasks.qmd
Normal file
8
material/2_tue/git/tasks.qmd
Normal file
@ -0,0 +1,8 @@
|
||||
# Tasks
|
||||
|
||||
1. Work with any forge that you like and create a user account (we strongly recommend GitHub since we will need it later again).
|
||||
2. Push your package `MyStatsPackage` to a remote repository.
|
||||
3. Add a function `printOwner` to the package through a pull request. The function should print your (GitHub) user name (hard-coded).
|
||||
4. Use the package from somebody else in the classroom and verify with `printOwner` that you use the correct package.
|
||||
5. Fork this other package and contribute a function `printContributor` to it via a PR. Get a review and get it merged.
|
||||
6. Add more functions to other packages of classmates that print funny things, but always ensure a linear history.
|
@ -1,120 +0,0 @@
|
||||
---
|
||||
type: slide
|
||||
slideOptions:
|
||||
transition: slide
|
||||
width: 1400
|
||||
height: 900
|
||||
margin: 0.1
|
||||
---
|
||||
|
||||
<style>
|
||||
.reveal strong {
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
.reveal p {
|
||||
text-align: left;
|
||||
}
|
||||
.reveal section h1 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal section h2 {
|
||||
color: orange;
|
||||
}
|
||||
.reveal code {
|
||||
font-family: 'Ubuntu Mono';
|
||||
color: orange;
|
||||
}
|
||||
</style>
|
||||
|
||||
# Working in Teams / Git Workflows
|
||||
|
||||
---
|
||||
|
||||
## Why Workflows?
|
||||
|
||||
- Git offers a lot of flexibility in managing changes.
|
||||
- When working in a team, some agreements need to be made however (especially on how to work with branches).
|
||||
|
||||
---
|
||||
|
||||
## Which Workflow?
|
||||
|
||||
- There are standard solutions.
|
||||
- It depends on the size of the team.
|
||||
- Workflow should enhance effectiveness of team, not be a burden that limits productivity.
|
||||
|
||||
---
|
||||
|
||||
## Centralized Workflow
|
||||
|
||||
- Only one branch: the `main` branch
|
||||
- Keep your changes in local commits till some feature is ready
|
||||
- If ready, directly push to `main`; no PRs, no reviews
|
||||
- Conflicts: fix locally (push not allowed anyway), use `git pull --rebase`
|
||||
- **Good for**: small teams, small projects, projects that are anyway reviewed over and over again
|
||||
- Example: LaTeX papers
|
||||
- Put each section in separate file
|
||||
- Put each sentence in separate line
|
||||
|
||||
---
|
||||
|
||||
## Feature Branch Workflow
|
||||
|
||||
- Each feature (or bugfix) in separate branch
|
||||
- Push feature branch to remote, use descriptive name
|
||||
- e.g. issue number in name if each branch closes one issue
|
||||
- `main` should never contain broken code
|
||||
- Protect direct push to `main`
|
||||
- PR (or MR) with review to merge from feature branch to `main`
|
||||
- Rebase feature branch on `main` if necessary
|
||||
- Delete remote branch once merged and no longer needed (one click on GitHub after merge)
|
||||
- **Good for**: small teams, small projects, prototyping, websites (continuous deployment), documentation
|
||||
- Aka. [trunk-based development](https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development) or [GitHub flow](https://guides.github.com/introduction/flow/)
|
||||
|
||||
---
|
||||
|
||||
## Gitflow
|
||||
|
||||
- [Visualization by Vincent Driessen](https://nvie.com/img/git-model@2x.png), from [original blog post in 2010](https://nvie.com/posts/a-successful-git-branching-model/)
|
||||
- `main` and `develop`
|
||||
- `main` contains releases as tags
|
||||
- `develop` contains latest features
|
||||
- Feature branches created of `develop`, PRs back to `develop`
|
||||
- Protect `main` and (possibly) `develop` from direct pushes
|
||||
- Dedicated release branches (e.g., `v1.0`) created of `develop`
|
||||
- Tested, fixed, merged to `main`
|
||||
- Afterwards, tagged, merged back to `develop`
|
||||
- Hotfix branches directly of and to `main`
|
||||
- **Good for**: software with users, larger teams
|
||||
- There is a tool `git-flow`, a wrapper around `git`, e.g. `git flow init` ... but not really necessary IMHO
|
||||
|
||||
---
|
||||
|
||||
## Forking Workflow
|
||||
|
||||
- Gitflow + feature branches on other forks
|
||||
- More control over access rights, distinguish between maintainers and external contributors
|
||||
- Should maintainers also use branches on their forks?
|
||||
- Makes overview of branches easier
|
||||
- Distinguishes between prototype branches (on fork, no PR), serious enhancements (on fork with PR), joint enhancements (on upstream)
|
||||
- **Good for**: open-source projects with external contributions (used more or less in preCICE)
|
||||
|
||||
---
|
||||
|
||||
## Do Small PRs
|
||||
|
||||
- For all workflows, it is better to do small PRs
|
||||
- Easier to review
|
||||
- Faster to merge --> fewer conflicts
|
||||
- Easier to squash
|
||||
|
||||
---
|
||||
|
||||
## Quick reads
|
||||
|
||||
- [Atlassian docs on workflows](https://www.atlassian.com/git/tutorials/comparing-workflows)
|
||||
- [Original gitflow blog post](https://nvie.com/posts/a-successful-git-branching-model/)
|
||||
- [Trunk-based development](https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development)
|
||||
- [GitHub flow](https://guides.github.com/introduction/flow/)
|
||||
- [How to keep pull requests manageable](https://gist.github.com/sktse/569cb192ce1518f83db58567591e3205)
|
@ -1,7 +1,12 @@
|
||||
|
||||
::: callout
|
||||
[Link to Slides](slides.qmd)
|
||||
:::
|
||||
|
||||
## Task 1
|
||||
|
||||
Solve [task 1](tasks.qmd#1)
|
||||
|
||||
----
|
||||
# Documenter.jl
|
||||
|
||||
### File-structure overview
|
||||
@ -76,6 +81,13 @@ Using `Literate.jl` one does not need to write `.md` files - but rather can use
|
||||
## this is a comment
|
||||
```
|
||||
|
||||
|
||||
## Task 2
|
||||
|
||||
Solve [task 2](tasks.qmd#2)
|
||||
|
||||
----
|
||||
|
||||
# PkgTemplate.jl
|
||||
```julia
|
||||
]activate --temp
|
||||
@ -104,4 +116,9 @@ You can also run the PkgTemplate interactively using
|
||||
Template(interactive=true)("MyPkg")
|
||||
```
|
||||
Which will ask you a hundred million questions ;-)
|
||||
:::
|
||||
:::
|
||||
|
||||
|
||||
## Task 3
|
||||
|
||||
Solve [task 3](tasks.qmd#3)
|
||||
|
84
material/3_wed/docs/tasks.ipynb
Normal file
84
material/3_wed/docs/tasks.ipynb
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Task 1: Docstrings {#1}\n",
|
||||
"1. Add Docstrings to some of your functions.\n",
|
||||
"2. Load the package, and check you can see the docstrings using e.g. `?rse_mean`\n",
|
||||
"\n",
|
||||
"-----\n",
|
||||
"\n",
|
||||
"# Task 2: Documenter.jl {#1}\n",
|
||||
"### Folderstructure\n",
|
||||
"1. create folders/files:\n",
|
||||
"```\n",
|
||||
"docs/\n",
|
||||
"├── src/\n",
|
||||
"├── src/mydocs.jl\n",
|
||||
"└── make.jl\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### add some docs\n",
|
||||
"2. with mydocs containing\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"````{verbatim}\n",
|
||||
"```@docs \n",
|
||||
"func(x)\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"````\n",
|
||||
"\n",
|
||||
"and\n",
|
||||
"\n",
|
||||
"`make.jl` containing\n",
|
||||
"```julia\n",
|
||||
"using Documenter, Example\n",
|
||||
"\n",
|
||||
"makedocs(sitename=\"My Documentation\")\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### 3. Generate\n",
|
||||
"Generate the docs using `include(\"make.jl\")` after activating the `./docs/Project.toml`\n",
|
||||
"\n",
|
||||
":::callout\n",
|
||||
"## Bonus-Task\n",
|
||||
" Use [`LiveServer.jl`](https://github.com/tlienart/LiveServer.jl) to automatically update a local preview of your documentation (follow [this tutorial](https://github.com/tlienart/LiveServer.jl#serve-docs) )\n",
|
||||
":::\n",
|
||||
"\n",
|
||||
"### 4. Add a tutorial\n",
|
||||
"Now add a tutorial `./docs/src/tutorial.md` which should contain a brief example simulating some data (using `rand`) and calculating mean, tstat and std on them.\n",
|
||||
"\n",
|
||||
"Use the `makedocs(...page=)` keywordargument.\n",
|
||||
"\n",
|
||||
"----\n",
|
||||
"\n",
|
||||
"# Task 3: PkgTemplate.jl {#3}\n",
|
||||
"Generate a package MySecondStatsPackage using PkgTemplate. \n",
|
||||
"\n",
|
||||
"- Add github-actions for:\n",
|
||||
" - coverage\n",
|
||||
" - unittests\n",
|
||||
" - docs\n",
|
||||
"- MIT license\n",
|
||||
"- README.md\n",
|
||||
"\n",
|
||||
"::: callout-tipp\n",
|
||||
" Don't forget to activate the github-page in the github settings!\n",
|
||||
":::"
|
||||
],
|
||||
"id": "7ec915e0"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"language": "python",
|
||||
"display_name": "Python 3 (ipykernel)"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
# Task 1: Docstrings {#1}
|
||||
1. Add Docstrings to some of your functions.
|
||||
2. Load the package, and check you can see the docstrings using e.g. `?rse_mean`
|
||||
|
||||
-----
|
||||
|
||||
# Task 2: Documenter.jl {#1}
|
||||
### Folderstructure
|
||||
1. create folders/files:
|
||||
```
|
||||
docs/
|
||||
├── src/
|
||||
├── src/mydocs.jl
|
||||
└── make.jl
|
||||
```
|
||||
|
||||
### add some docs
|
||||
2. with mydocs containing
|
||||
|
||||
````{verbatim}
|
||||
```@docs
|
||||
func(x)
|
||||
```
|
||||
````
|
||||
|
||||
and
|
||||
|
||||
`make.jl` containing
|
||||
```julia
|
||||
using Documenter, Example
|
||||
|
||||
makedocs(sitename="My Documentation")
|
||||
```
|
||||
|
||||
### 3. Generate
|
||||
Generate the docs using `include("make.jl")` after activating the `./docs/Project.toml`
|
||||
|
||||
:::callout
|
||||
## Bonus-Task
|
||||
Use [`LiveServer.jl`](https://github.com/tlienart/LiveServer.jl) to automatically update a local preview of your documentation (follow [this tutorial](https://github.com/tlienart/LiveServer.jl#serve-docs) )
|
||||
:::
|
||||
|
||||
### 4. Add a tutorial
|
||||
Now add a tutorial `./docs/src/tutorial.md` which should contain a brief example simulating some data (using `rand`) and calculating mean, tstat and std on them.
|
||||
|
||||
Use the `makedocs(...page=)` keywordargument.
|
||||
|
||||
----
|
||||
|
||||
# Task 3: PkgTemplate.jl {#3}
|
||||
Generate a package MySecondStatsPackage using PkgTemplate.
|
||||
|
||||
- Add github-actions for:
|
||||
- coverage
|
||||
- unittests
|
||||
- docs
|
||||
- MIT license
|
||||
- README.md
|
||||
|
||||
::: callout-tipp
|
||||
Don't forget to activate the github-page in the github settings!
|
||||
:::
|
@ -1,7 +1,11 @@
|
||||
---
|
||||
|
||||
---
|
||||
## Slides
|
||||
|
||||
The slides are available [in pptx format here](Julia_Matrices_Optimization_JuMP_Stuttgart2023.pptx). Note that there are a few extra slides in case you are motivated to learn more!
|
||||
|
||||
## Exercise
|
||||
|
||||
The exercise is available [as a jupyter-notebook here](Julia_Matrices_Optimization_JuMP_Stuttgart2023.ipynb).
|
||||
The exercise is rendered [as html here](Julia_Matrices_Optimization_JuMP_Stuttgart2023.ipynb) but can also be downloaded {{< downloadthis Julia_Matrices_Optimization_JuMP_Stuttgart2023.ipynb label="Download as ipynb" >}}
|
||||
|
@ -229,7 +229,17 @@ data(penguins) * mapping(:bill_length_mm, :bill_depth_mm, color=:species) * (lin
|
||||
data(penguins) * mapping(:bill_length_mm, :bill_depth_mm, color=:species) * (smooth() + visual(Scatter)) |> draw
|
||||
```
|
||||
|
||||
### Advanced
|
||||
|
||||
```julia
|
||||
h = data(penguins) * mapping(:bill_length_mm, :bill_depth_mm, color=:species) * (smooth() + visual(Scatter)) |> draw
|
||||
h.grid
|
||||
ax = h.grid[1,1].axis
|
||||
ax + tab -> ax.xticks
|
||||
h
|
||||
```
|
||||
|
||||
## Task 3
|
||||
|
||||
[Click here for the next task](tasks.qmd#3)
|
||||
[Click here for the next task](tasks.qmd#3)
|
||||
|
||||
|
@ -158,4 +158,40 @@ PlutoExtras.BondTable([
|
||||
```
|
||||
:::
|
||||
|
||||
# Task 3: AlgebraOfGraphics
|
||||
# Task 3: AlgebraOfGraphics
|
||||
|
||||
For this task we need a dataset, and I choose the US EGG dataset for it's simplicity for you.
|
||||
|
||||
to load the data, use the following code
|
||||
```julia
|
||||
using DataFrames, HTTP, CSV
|
||||
# dataset via https://github.com/rfordatascience/tidytuesday/tree/master
|
||||
df = CSV.read(download("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2023/2023-04-11/egg-production.csv"),DataFrame)
|
||||
```
|
||||
|
||||
:::callout-tip
|
||||
## If you dislike Pluto.jl
|
||||
If you dont like to use Pluto.jl, you can of course switch back to VSCode. Then you have to create a new environment and add the packages you use before.
|
||||
:::
|
||||
|
||||
|
||||
## 🥚 vs. 🗓
|
||||
Visualize the number of eggs against the year
|
||||
|
||||
:::callout-tip
|
||||
To get a first overview, `first(df)` , `describe(df)` and `names(df)` are typically helpful
|
||||
:::
|
||||
|
||||
## Split them up
|
||||
Next split them up, choose `color` and `col` and choose reasonable columns from the dataset
|
||||
|
||||
## Rotate the labels
|
||||
Use the trick from the handout to modify a plot after it was generated: Rotate the x-label ticks by some 30°
|
||||
|
||||
:::callout-tip
|
||||
instead of rotating each axis manually, you can also replace the `draw` command in your pipeline with an anonymous function. This allows you to specify additional arguments e.g. to the axis, for all "sub"-plots
|
||||
```julia
|
||||
... |> x-> draw(x;axis=(;xlims = (-3,2))) # <1>
|
||||
```
|
||||
1. Note the `;` before xlims, this enforces that a `NamedTuple` is created
|
||||
|
||||
|
@ -4,4 +4,5 @@ The slides are available [in pptx format here](Julia_Parallel_Distributed_2023_S
|
||||
|
||||
## Exercise
|
||||
|
||||
The exercise is available [as a jupyter-notebook here](Julia_Parallel_Distributed_2023_Stuttgart.ipynb).
|
||||
The exercise is rendered [as html here](Julia_Parallel_Distributed_2023_Stuttgart.ipynb) but can also be downloaded {{< downloadthis Julia_Parallel_Distributed_2023_Stuttgart.ipynb label="Download as ipynb" >}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user