{ "cells": [ { "cell_type": "markdown", "id": "ec58981d-e34d-4256-b218-e2d26b663ad5", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "# Setting up Git and GitHub\n", "\n", "## Learning Objectives\n", "- Understand how to set up Git on a new computer\n", "- Configure Git with user information and preferred settings\n", "- Learn to set up a text editor for use with Git\n", "- Configure Git to use a specific branch naming convention\n", "- Set up GitHub credentials using Personal Access Tokens (PATs) for secure communication with GitHub\n", "\n", "\n", "## Setting up Git\n", "\n", "When we use Git on a new computer for the first time,\n", "we need to configure a few things. Below are a few examples\n", "of configurations we will set as we get started with Git:\n", "\n", "* Our name and email address.\n", "* Our preferred text editor to use with Git.\n", "\n", "We will also want to set these settings _globally_, meaning they'll apply\n", "whenever we use Git within a given user account on a specific computer.\n", "\n", "\n", "### Username and email address\n", "\n", "On a command line, Git commands are written as `git `,\n", "where `` is what we actually want to do and `` is additional\n", "optional information which may be needed for the ``. To configure Git,\n", "settings, we use the 'command' `config`.\n", "\n", "``` bash\n", "$ git config --global user.name \"Joe Bloggs\"\n", "$ git config --global user.email \"joe.bloggs@example.net\"\n", "```\n", "\n", "This user name and email will be associated with your subsequent Git activity,\n", "which means that any changes pushed to GitHub (or another Git host server)\n", "from here on will include this information. If you work on public repositories\n", "with these services, then this information will be publicly visible.\n", "\n", "For this course, we will be interacting with GitHub and\n", "so the email address used should be the same as the one used when setting up\n", "your GitHub account. If you are concerned about privacy, please review\n", "GitHub's instructions for keeping your email address private. \n", "\n", "#### Keeping your email private\n", "If you elect to use a private email address with GitHub, then use that same email address for the `user.email` value. It will look something like `nnnnnnnnn-username@users.noreply.github.com` where `nnnnnnnnn-username` is your username prepended with a numerical ID.\n", "\n", "The commands we just ran above only need to be run once: the flag `--global` tells Git\n", "to use the settings for _every_ Git repository, in your user account, on this computer.\n", "\n", "You can check your global settings at any time:\n", "\n", "``` bash\n", "$ git config --global --list\n", "```\n", "\n", "\n", "### Setting a text editor\n", "\n", "Git will use a text editor installed on your computer when you need to write\n", "'commit messages' (more about these later in the course). The default editor on\n", "most operating systems is set to Vim which, while powerful, is difficult to\n", "use if you're not used to it. You can configure Git to instead use something\n", "you're more familiar with. We recommend trying *Nano* for Git usage: it works\n", "at the command line, making it quick to write the short 'commit messages', but\n", "is much simpler to use than Vim. You can change this at any time if you find you'd\n", "prefer to use something else.\n", "\n", "#### Git for Windows\n", "If you followed the recommendations in the installation instructions for Git for Windows then Nano should already be the default editor used by Git.\n", "\n", "The following table shows how to set the default editor for Git for several popular \n", "text editors. In some cases, you need to provide the path to the executable\n", "program for the editor — this indicated as `path/to/executable-name` (if you\n", "aren't sure what to enter for this, do some online searching or try a search\n", "on your computer).\n", "\n", "| Editor | Configuration command |\n", "|:-------------------|:-------------------------------------------------|\n", "| Atom | `$ git config --global core.editor \"atom --wait\"`|\n", "| nano | `$ git config --global core.editor \"nano -w\"` |\n", "| BBEdit (Mac, with command line tools) | `$ git config --global core.editor \"bbedit -w\"` |\n", "| Sublime Text (Mac) | `$ git config --global core.editor \"'path/to/subl' -n -w\"` |\n", "| Sublime Text (Win) | `$ git config --global core.editor \"'path/to/sublime_text.exe' -w\"` |\n", "| Notepad (Win) | `$ git config --global core.editor \"path/to/notepad.exe\"`|\n", "| Notepad++ (Win) | `$ git config --global core.editor \"'path/to/notepad++.exe' -multiInst -notabbar -nosession -noPlugin\"`|\n", "| Kate (Linux) | `$ git config --global core.editor \"kate\"` |\n", "| Gedit (Linux) | `$ git config --global core.editor \"gedit --wait --new-window\"` |\n", "| Scratch (Linux) | `$ git config --global core.editor \"scratch-text-editor\"` |\n", "| Emacs | `$ git config --global core.editor \"emacs\"` |\n", "| Vim | `$ git config --global core.editor \"vim\"` |\n", "| VS Code | `$ git config --global core.editor \"code --wait\"` |\n", "\n", "\n", "### Default Git branch naming\n", "\n", "Git (2.28+) allows configuration of the name of the branch created when you\n", "initialize any new repository. (We will cover *repositories* and *branches* later\n", "in the course; for now, you can think of a repository as a place where files\n", "for a project are kept and a branch just as a name for the development history\n", "of the files in the repository.)\n", "\n", "By default, Git will create a branch called `master` whenever\n", "you start a new repository. However, there is a move in the\n", "software development community\n", "to stop using this term in favour of more inclusive language. Most Git\n", "code hosting services have transitioned to using `main` as the default \n", "branch; for example, any new repository that is opened in GitHub defaults \n", "to using `main` for the name of the branch.\n", "\n", "At the time of writing, Git has not yet made the same change. For this course,\n", "we will manually configure Git to use the same `main` branch name as most cloud\n", "services.\n", "\n", "For versions of Git 2.28 or later, this can be achieved by setting the\n", "`init.defaultBranch` setting to `main` globally:\n", "\n", "``` bash\n", "$ git config --global init.defaultBranch main\n", "```\n", "\n", "For versions of Git prior to 2.28, the change can only be made on an individual\n", "repository level and we will cover this in the episode on making repositories later.\n", "\n", "#### Exercise\n", "Set up Git the way you'd like it by configuring settings for:\n", "- Your username and password\n", "- Your preferred text editor\n", "- (Optional) Changing the default Git branch naming to `main`. If your version of Git is older than 2.28 then you won't be able to do this yet.\n", "\n", "\n", "### Final notes on configuring Git\n", "\n", "#### Changing configuration\n", "\n", "You can change your configuration as many times as you want. Use the\n", "same commands as above to choose another editor, update your email address, etc.\n", "\n", "#### Git Help and Manual\n", "\n", "Always remember that if you forget the subcommands or options of a `git` command,\n", "you can access the relevant list of options typing `git -h`, or\n", "access the corresponding Git manual by typing\n", "`git --help`, e.g.:\n", "\n", "``` bash\n", "$ git config -h\n", "$ git config --help\n", "```\n", "\n", "While viewing the manual, remember the `:` is a prompt waiting for commands and\n", "you can press Q to exit the manual.\n", "More generally, you can get the list of available `git` commands and further\n", "resources of the Git manual typing:\n", "``` bash\n", "$ git help\n", "```\n", " \n", "#### On line Endings\n", "\n", "You can sometimes run into issues when using Git if you (or your team) use\n", "different machines when working on files. If you've followed the default\n", "installation instructions (particularly Windows users who are using\n", "*Git for Windows*) then you shouldn't run into problems, but we include a note\n", "here in case you run into trouble.\n", "\n", "As with other keys, when you hit Enter / / Return\n", "on your keyboard, your computer encodes this input as a character.\n", "Different operating systems use different character(s) to represent the end of a line.\n", "(You may also hear these referred to as newlines or line breaks.)\n", "Because Git uses these characters to compare files,\n", "it may cause unexpected issues when editing a file on different machines. \n", "You can change the way Git recognizes and encodes line endings\n", "using the `core.autocrlf` setting. The following settings are\n", "recommended:\n", "\n", "- **MacOS and Linux:**\n", " ``` bash\n", " $ git config --global core.autocrlf input\n", " ```\n", "- **Windows:**\n", " ``` bash\n", " $ git config --global core.autocrlf true\n", " ```\n", "You can read more about this issue \n", "in the Pro Git book.\n", "\n", "\n", "## Setting up GitHub credentials\n", "\n", "We're going to be using Git to communicate with GitHub. To do this, we need to\n", "set up some authentication to enable us to do this securely. The approach we\n", "will adopt in this course is to use GitHub\n", "Personal Access Tokens (PAT). A PAT is\n", "a way for some application or service (such as Git) to identify itself as being\n", "authorised to interact with GitHub on your behalf. Part of setting up a PAT\n", "involves specifying the scope of what an application using that PAT is allowed\n", "to do on GitHub. PATs are generated via your GitHub account and then saved for\n", "later use.\n", "\n", "### Look after your PATs!\n", "**You should treat PATs with the same level of care you would your GitHub account password. Store the PAT somewhere secure, such as in a password manager. If you ever suspect a PAT has been exposed, then you should delete the token and generate a new one.**\n", "### Note on classic tokens\n", "At the time of writing, GitHub is in the process of creating newer, so-called 'fine grained' personal access tokens. These provide more fine grained control, but are still in a developmental phase. The older, 'classic', PATs are sufficient forour needs and simpler to set up, so we'll use them throughout this course.\n", "\n", "To set up a PAT, follow the\n", "instructions provided by GitHub for classic tokens. Note:\n", "\n", "- You will need to set an expiration time on your token. This is part of the\n", " GitHub's security and we strongly suggest you don't select the _No expiration_\n", " option, however tempting it may be. You should get an email a few days before the\n", " token expires reminding you to regenerate it.\n", "\n", "- As part of the process, you will need to select scopes for the token.\n", " All that is needed for this course (and likely for your standard, day-to-day use\n", " of Git) is to select the **repo** option: this will enable you to interact\n", " with repositories you have been invited to work on. More information about\n", " scopes can be found in the\n", " GitHub documentation on OAuth scopes.\n", "\n", "- You can regenerate a PAT that has expired\n", " by going to your _Personal access tokens (classic)_ as described\n", " in the link above, selecting the affected token and then clicking on _Regenerate token_.\n", "\n", "- To delete a token (e.g. if you suspect it's been compromised), go to your\n", " _Personal access tokens (classic)_ as described in the link above and then\n", " click on _Delete_ next to the token.\n", " \n", "### Exercise\n", "Using the instructions on GitHub linked above, create a PAT and save it somewhere safe for later use in this course.\n", "\n", "## Summary Quiz" ] }, { "cell_type": "code", "execution_count": 1, "id": "45f5d868-f0ce-4a74-b3a1-eb5aeddccf64", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [ "remove-input" ] }, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "var questionsUWZEHLQDlldB=[\n", " {\n", " \"question\": \"Which command is used to set the global username in Git?\",\n", " \"type\": \"many_choice\",\n", " \"answers\": [\n", " {\n", " \"answer\": \"git config --global core.editor \\\"nano -w\\\"\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " },\n", " {\n", " \"answer\": \"git config --global user.name \\\"Joe Bloggs\\\"\",\n", " \"correct\": true,\n", " \"feedback\": \"Correct\"\n", " },\n", " {\n", " \"answer\": \"git config --global user.email \\\"joe.bloggs@example.net\\\"\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " },\n", " {\n", " \"answer\": \"git config --global init.defaultBranch main\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " }\n", " ]\n", " },\n", " {\n", " \"question\": \"Why might you want to keep your email address private when using GitHub?\",\n", " \"type\": \"many_choice\",\n", " \"answers\": [\n", " {\n", " \"answer\": \"To avoid receiving spam\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " },\n", " {\n", " \"answer\": \"To prevent unauthorized access to your GitHub account\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " },\n", " {\n", " \"answer\": \"To ensure commits are attributed to the correct user\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " },\n", " {\n", " \"answer\": \"To use a generic email provided by GitHub for privacy reasons\",\n", " \"correct\": true,\n", " \"feedback\": \"Correct\"\n", " }\n", " ]\n", " },\n", " {\n", " \"question\": \"What is the recommended default brnach name for new Git repositories\",\n", " \"type\": \"many_choice\",\n", " \"answers\": [\n", " {\n", " \"answer\": \"master\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " },\n", " {\n", " \"answer\": \"main\",\n", " \"correct\": true,\n", " \"feedback\": \"Correct\"\n", " },\n", " {\n", " \"answer\": \"default\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " },\n", " {\n", " \"answer\": \"primary\",\n", " \"correct\": false,\n", " \"feedback\": \"Incorrect\"\n", " }\n", " ]\n", " }\n", "];\n", " // Make a random ID\n", "function makeid(length) {\n", " var result = [];\n", " var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n", " var charactersLength = characters.length;\n", " for (var i = 0; i < length; i++) {\n", " result.push(characters.charAt(Math.floor(Math.random() * charactersLength)));\n", " }\n", " return result.join('');\n", "}\n", "\n", "// Choose a random subset of an array. Can also be used to shuffle the array\n", "function getRandomSubarray(arr, size) {\n", " var shuffled = arr.slice(0), i = arr.length, temp, index;\n", " while (i--) {\n", " index = Math.floor((i + 1) * Math.random());\n", " temp = shuffled[index];\n", " shuffled[index] = shuffled[i];\n", " shuffled[i] = temp;\n", " }\n", " return shuffled.slice(0, size);\n", "}\n", "\n", "function printResponses(responsesContainer) {\n", " var responses=JSON.parse(responsesContainer.dataset.responses);\n", " var stringResponses='IMPORTANT!To preserve this answer sequence for submission, when you have finalized your answers:
  1. Copy the text in this cell below \"Answer String\"
  2. Double click on the cell directly below the Answer String, labeled \"Replace Me\"
  3. Select the whole \"Replace Me\" text
  4. Paste in your answer string and press shift-Enter.
  5. Save the notebook using the save icon or File->Save Notebook menu item



  6. Answer String:
    ';\n", " console.log(responses);\n", " responses.forEach((response, index) => {\n", " if (response) {\n", " console.log(index + ': ' + response);\n", " stringResponses+= index + ': ' + response +\"
    \";\n", " }\n", " });\n", " responsesContainer.innerHTML=stringResponses;\n", "}\n", "function check_mc() {\n", " var id = this.id.split('-')[0];\n", " //var response = this.id.split('-')[1];\n", " //console.log(response);\n", " //console.log(\"In check_mc(), id=\"+id);\n", " //console.log(event.srcElement.id) \n", " //console.log(event.srcElement.dataset.correct) \n", " //console.log(event.srcElement.dataset.feedback)\n", "\n", " var label = event.srcElement;\n", " //console.log(label, label.nodeName);\n", " var depth = 0;\n", " while ((label.nodeName != \"LABEL\") && (depth < 20)) {\n", " label = label.parentElement;\n", " console.log(depth, label);\n", " depth++;\n", " }\n", "\n", "\n", "\n", " var answers = label.parentElement.children;\n", "\n", " //console.log(answers);\n", "\n", "\n", " // Split behavior based on multiple choice vs many choice:\n", " var fb = document.getElementById(\"fb\" + id);\n", "\n", "\n", "\n", "\n", " if (fb.dataset.numcorrect == 1) {\n", " // What follows is for the saved responses stuff\n", " var outerContainer = fb.parentElement.parentElement;\n", " var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n", " if (responsesContainer) {\n", " //console.log(responsesContainer);\n", " var response = label.firstChild.innerText;\n", " if (label.querySelector(\".QuizCode\")){\n", " response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n", " }\n", " console.log(response);\n", " //console.log(document.getElementById(\"quizWrap\"+id));\n", " var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n", " console.log(\"Question \" + qnum);\n", " //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n", " var responses=JSON.parse(responsesContainer.dataset.responses);\n", " console.log(responses);\n", " responses[qnum]= response;\n", " responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n", " printResponses(responsesContainer);\n", " }\n", " // End code to preserve responses\n", " \n", " for (var i = 0; i < answers.length; i++) {\n", " var child = answers[i];\n", " //console.log(child);\n", " child.className = \"MCButton\";\n", " }\n", "\n", "\n", "\n", " if (label.dataset.correct == \"true\") {\n", " // console.log(\"Correct action\");\n", " if (\"feedback\" in label.dataset) {\n", " fb.textContent = jaxify(label.dataset.feedback);\n", " } else {\n", " fb.textContent = \"Correct!\";\n", " }\n", " label.classList.add(\"correctButton\");\n", "\n", " fb.className = \"Feedback\";\n", " fb.classList.add(\"correct\");\n", "\n", " } else {\n", " if (\"feedback\" in label.dataset) {\n", " fb.textContent = jaxify(label.dataset.feedback);\n", " } else {\n", " fb.textContent = \"Incorrect -- try again.\";\n", " }\n", " //console.log(\"Error action\");\n", " label.classList.add(\"incorrectButton\");\n", " fb.className = \"Feedback\";\n", " fb.classList.add(\"incorrect\");\n", " }\n", " }\n", " else {\n", " var reset = false;\n", " var feedback;\n", " if (label.dataset.correct == \"true\") {\n", " if (\"feedback\" in label.dataset) {\n", " feedback = jaxify(label.dataset.feedback);\n", " } else {\n", " feedback = \"Correct!\";\n", " }\n", " if (label.dataset.answered <= 0) {\n", " if (fb.dataset.answeredcorrect < 0) {\n", " fb.dataset.answeredcorrect = 1;\n", " reset = true;\n", " } else {\n", " fb.dataset.answeredcorrect++;\n", " }\n", " if (reset) {\n", " for (var i = 0; i < answers.length; i++) {\n", " var child = answers[i];\n", " child.className = \"MCButton\";\n", " child.dataset.answered = 0;\n", " }\n", " }\n", " label.classList.add(\"correctButton\");\n", " label.dataset.answered = 1;\n", " fb.className = \"Feedback\";\n", " fb.classList.add(\"correct\");\n", "\n", " }\n", " } else {\n", " if (\"feedback\" in label.dataset) {\n", " feedback = jaxify(label.dataset.feedback);\n", " } else {\n", " feedback = \"Incorrect -- try again.\";\n", " }\n", " if (fb.dataset.answeredcorrect > 0) {\n", " fb.dataset.answeredcorrect = -1;\n", " reset = true;\n", " } else {\n", " fb.dataset.answeredcorrect--;\n", " }\n", "\n", " if (reset) {\n", " for (var i = 0; i < answers.length; i++) {\n", " var child = answers[i];\n", " child.className = \"MCButton\";\n", " child.dataset.answered = 0;\n", " }\n", " }\n", " label.classList.add(\"incorrectButton\");\n", " fb.className = \"Feedback\";\n", " fb.classList.add(\"incorrect\");\n", " }\n", " // What follows is for the saved responses stuff\n", " var outerContainer = fb.parentElement.parentElement;\n", " var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n", " if (responsesContainer) {\n", " //console.log(responsesContainer);\n", " var response = label.firstChild.innerText;\n", " if (label.querySelector(\".QuizCode\")){\n", " response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n", " }\n", " console.log(response);\n", " //console.log(document.getElementById(\"quizWrap\"+id));\n", " var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n", " console.log(\"Question \" + qnum);\n", " //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n", " var responses=JSON.parse(responsesContainer.dataset.responses);\n", " if (label.dataset.correct == \"true\") {\n", " if (typeof(responses[qnum]) == \"object\"){\n", " if (!responses[qnum].includes(response))\n", " responses[qnum].push(response);\n", " } else{\n", " responses[qnum]= [ response ];\n", " }\n", " } else {\n", " responses[qnum]= response;\n", " }\n", " console.log(responses);\n", " responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n", " printResponses(responsesContainer);\n", " }\n", " // End save responses stuff\n", "\n", "\n", "\n", " var numcorrect = fb.dataset.numcorrect;\n", " var answeredcorrect = fb.dataset.answeredcorrect;\n", " if (answeredcorrect >= 0) {\n", " fb.textContent = feedback + \" [\" + answeredcorrect + \"/\" + numcorrect + \"]\";\n", " } else {\n", " fb.textContent = feedback + \" [\" + 0 + \"/\" + numcorrect + \"]\";\n", " }\n", "\n", "\n", " }\n", "\n", " if (typeof MathJax != 'undefined') {\n", " var version = MathJax.version;\n", " console.log('MathJax version', version);\n", " if (version[0] == \"2\") {\n", " MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n", " } else if (version[0] == \"3\") {\n", " MathJax.typeset([fb]);\n", " }\n", " } else {\n", " console.log('MathJax not detected');\n", " }\n", "\n", "}\n", "\n", "function make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id) {\n", " var shuffled;\n", " if (shuffle_answers == \"True\") {\n", " //console.log(shuffle_answers+\" read as true\");\n", " shuffled = getRandomSubarray(qa.answers, qa.answers.length);\n", " } else {\n", " //console.log(shuffle_answers+\" read as false\");\n", " shuffled = qa.answers;\n", " }\n", "\n", "\n", " var num_correct = 0;\n", "\n", "\n", "\n", " shuffled.forEach((item, index, ans_array) => {\n", " //console.log(answer);\n", "\n", " // Make input element\n", " var inp = document.createElement(\"input\");\n", " inp.type = \"radio\";\n", " inp.id = \"quizo\" + id + index;\n", " inp.style = \"display:none;\";\n", " aDiv.append(inp);\n", "\n", " //Make label for input element\n", " var lab = document.createElement(\"label\");\n", " lab.className = \"MCButton\";\n", " lab.id = id + '-' + index;\n", " lab.onclick = check_mc;\n", " var aSpan = document.createElement('span');\n", " aSpan.classsName = \"\";\n", " //qDiv.id=\"quizQn\"+id+index;\n", " if (\"answer\" in item) {\n", " aSpan.innerHTML = jaxify(item.answer);\n", " //aSpan.innerHTML=item.answer;\n", " }\n", " lab.append(aSpan);\n", "\n", " // Create div for code inside question\n", " var codeSpan;\n", " if (\"code\" in item) {\n", " codeSpan = document.createElement('span');\n", " codeSpan.id = \"code\" + id + index;\n", " codeSpan.className = \"QuizCode\";\n", " var codePre = document.createElement('pre');\n", " codeSpan.append(codePre);\n", " var codeCode = document.createElement('code');\n", " codePre.append(codeCode);\n", " codeCode.innerHTML = item.code;\n", " lab.append(codeSpan);\n", " //console.log(codeSpan);\n", " }\n", "\n", " //lab.textContent=item.answer;\n", "\n", " // Set the data attributes for the answer\n", " lab.setAttribute('data-correct', item.correct);\n", " if (item.correct) {\n", " num_correct++;\n", " }\n", " if (\"feedback\" in item) {\n", " lab.setAttribute('data-feedback', item.feedback);\n", " }\n", " lab.setAttribute('data-answered', 0);\n", "\n", " aDiv.append(lab);\n", "\n", " });\n", "\n", " if (num_correct > 1) {\n", " outerqDiv.className = \"ManyChoiceQn\";\n", " } else {\n", " outerqDiv.className = \"MultipleChoiceQn\";\n", " }\n", "\n", " return num_correct;\n", "\n", "}\n", "function check_numeric(ths, event) {\n", "\n", " if (event.keyCode === 13) {\n", " ths.blur();\n", "\n", " var id = ths.id.split('-')[0];\n", "\n", " var submission = ths.value;\n", " if (submission.indexOf('/') != -1) {\n", " var sub_parts = submission.split('/');\n", " //console.log(sub_parts);\n", " submission = sub_parts[0] / sub_parts[1];\n", " }\n", " //console.log(\"Reader entered\", submission);\n", "\n", " if (\"precision\" in ths.dataset) {\n", " var precision = ths.dataset.precision;\n", " // console.log(\"1:\", submission)\n", " submission = Math.round((1 * submission + Number.EPSILON) * 10 ** precision) / 10 ** precision;\n", " // console.log(\"Rounded to \", submission, \" precision=\", precision );\n", " }\n", "\n", "\n", " //console.log(\"In check_numeric(), id=\"+id);\n", " //console.log(event.srcElement.id) \n", " //console.log(event.srcElement.dataset.feedback)\n", "\n", " var fb = document.getElementById(\"fb\" + id);\n", " fb.style.display = \"none\";\n", " fb.textContent = \"Incorrect -- try again.\";\n", "\n", " var answers = JSON.parse(ths.dataset.answers);\n", " //console.log(answers);\n", "\n", " var defaultFB = \"\";\n", " var correct;\n", " var done = false;\n", " answers.every(answer => {\n", " //console.log(answer.type);\n", "\n", " correct = false;\n", " // if (answer.type==\"value\"){\n", " if ('value' in answer) {\n", " if (submission == answer.value) {\n", " if (\"feedback\" in answer) {\n", " fb.textContent = jaxify(answer.feedback);\n", " } else {\n", " fb.textContent = jaxify(\"Correct\");\n", " }\n", " correct = answer.correct;\n", " //console.log(answer.correct);\n", " done = true;\n", " }\n", " // } else if (answer.type==\"range\") {\n", " } else if ('range' in answer) {\n", " //console.log(answer.range);\n", " if ((submission >= answer.range[0]) && (submission < answer.range[1])) {\n", " fb.textContent = jaxify(answer.feedback);\n", " correct = answer.correct;\n", " //console.log(answer.correct);\n", " done = true;\n", " }\n", " } else if (answer.type == \"default\") {\n", " defaultFB = answer.feedback;\n", " }\n", " if (done) {\n", " return false; // Break out of loop if this has been marked correct\n", " } else {\n", " return true; // Keep looking for case that includes this as a correct answer\n", " }\n", " });\n", "\n", " if ((!done) && (defaultFB != \"\")) {\n", " fb.innerHTML = jaxify(defaultFB);\n", " //console.log(\"Default feedback\", defaultFB);\n", " }\n", "\n", " fb.style.display = \"block\";\n", " if (correct) {\n", " ths.className = \"Input-text\";\n", " ths.classList.add(\"correctButton\");\n", " fb.className = \"Feedback\";\n", " fb.classList.add(\"correct\");\n", " } else {\n", " ths.className = \"Input-text\";\n", " ths.classList.add(\"incorrectButton\");\n", " fb.className = \"Feedback\";\n", " fb.classList.add(\"incorrect\");\n", " }\n", "\n", " // What follows is for the saved responses stuff\n", " var outerContainer = fb.parentElement.parentElement;\n", " var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n", " if (responsesContainer) {\n", " console.log(submission);\n", " var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n", " //console.log(\"Question \" + qnum);\n", " //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n", " var responses=JSON.parse(responsesContainer.dataset.responses);\n", " console.log(responses);\n", " if (submission == ths.value){\n", " responses[qnum]= submission;\n", " } else {\n", " responses[qnum]= ths.value + \"(\" + submission +\")\";\n", " }\n", " responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n", " printResponses(responsesContainer);\n", " }\n", " // End code to preserve responses\n", "\n", " if (typeof MathJax != 'undefined') {\n", " var version = MathJax.version;\n", " console.log('MathJax version', version);\n", " if (version[0] == \"2\") {\n", " MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n", " } else if (version[0] == \"3\") {\n", " MathJax.typeset([fb]);\n", " }\n", " } else {\n", " console.log('MathJax not detected');\n", " }\n", " return false;\n", " }\n", "\n", "}\n", "\n", "function isValid(el, charC) {\n", " //console.log(\"Input char: \", charC);\n", " if (charC == 46) {\n", " if (el.value.indexOf('.') === -1) {\n", " return true;\n", " } else if (el.value.indexOf('/') != -1) {\n", " var parts = el.value.split('/');\n", " if (parts[1].indexOf('.') === -1) {\n", " return true;\n", " }\n", " }\n", " else {\n", " return false;\n", " }\n", " } else if (charC == 47) {\n", " if (el.value.indexOf('/') === -1) {\n", " if ((el.value != \"\") && (el.value != \".\")) {\n", " return true;\n", " } else {\n", " return false;\n", " }\n", " } else {\n", " return false;\n", " }\n", " } else if (charC == 45) {\n", " var edex = el.value.indexOf('e');\n", " if (edex == -1) {\n", " edex = el.value.indexOf('E');\n", " }\n", "\n", " if (el.value == \"\") {\n", " return true;\n", " } else if (edex == (el.value.length - 1)) { // If just after e or E\n", " return true;\n", " } else {\n", " return false;\n", " }\n", " } else if (charC == 101) { // \"e\"\n", " if ((el.value.indexOf('e') === -1) && (el.value.indexOf('E') === -1) && (el.value.indexOf('/') == -1)) {\n", " // Prev symbol must be digit or decimal point:\n", " if (el.value.slice(-1).search(/\\d/) >= 0) {\n", " return true;\n", " } else if (el.value.slice(-1).search(/\\./) >= 0) {\n", " return true;\n", " } else {\n", " return false;\n", " }\n", " } else {\n", " return false;\n", " }\n", " } else {\n", " if (charC > 31 && (charC < 48 || charC > 57))\n", " return false;\n", " }\n", " return true;\n", "}\n", "\n", "function numeric_keypress(evnt) {\n", " var charC = (evnt.which) ? evnt.which : evnt.keyCode;\n", "\n", " if (charC == 13) {\n", " check_numeric(this, evnt);\n", " } else {\n", " return isValid(this, charC);\n", " }\n", "}\n", "\n", "\n", "\n", "\n", "\n", "function make_numeric(qa, outerqDiv, qDiv, aDiv, id) {\n", "\n", "\n", "\n", " //console.log(answer);\n", "\n", "\n", " outerqDiv.className = \"NumericQn\";\n", " aDiv.style.display = 'block';\n", "\n", " var lab = document.createElement(\"label\");\n", " lab.className = \"InpLabel\";\n", " lab.textContent = \"Type numeric answer here:\";\n", " aDiv.append(lab);\n", "\n", " var inp = document.createElement(\"input\");\n", " inp.type = \"text\";\n", " //inp.id=\"input-\"+id;\n", " inp.id = id + \"-0\";\n", " inp.className = \"Input-text\";\n", " inp.setAttribute('data-answers', JSON.stringify(qa.answers));\n", " if (\"precision\" in qa) {\n", " inp.setAttribute('data-precision', qa.precision);\n", " }\n", " aDiv.append(inp);\n", " //console.log(inp);\n", "\n", " //inp.addEventListener(\"keypress\", check_numeric);\n", " //inp.addEventListener(\"keypress\", numeric_keypress);\n", " /*\n", " inp.addEventListener(\"keypress\", function(event) {\n", " return numeric_keypress(this, event);\n", " }\n", " );\n", " */\n", " //inp.onkeypress=\"return numeric_keypress(this, event)\";\n", " inp.onkeypress = numeric_keypress;\n", " inp.onpaste = event => false;\n", "\n", " inp.addEventListener(\"focus\", function (event) {\n", " this.value = \"\";\n", " return false;\n", " }\n", " );\n", "\n", "\n", "}\n", "function jaxify(string) {\n", " var mystring = string;\n", "\n", " var count = 0;\n", " var loc = mystring.search(/([^\\\\]|^)(\\$)/);\n", "\n", " var count2 = 0;\n", " var loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n", "\n", " //console.log(loc);\n", "\n", " while ((loc >= 0) || (loc2 >= 0)) {\n", "\n", " /* Have to replace all the double $$ first with current implementation */\n", " if (loc2 >= 0) {\n", " if (count2 % 2 == 0) {\n", " mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\[\");\n", " } else {\n", " mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\]\");\n", " }\n", " count2++;\n", " } else {\n", " if (count % 2 == 0) {\n", " mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\(\");\n", " } else {\n", " mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\)\");\n", " }\n", " count++;\n", " }\n", " loc = mystring.search(/([^\\\\]|^)(\\$)/);\n", " loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n", " //console.log(mystring,\", loc:\",loc,\", loc2:\",loc2);\n", " }\n", "\n", " //console.log(mystring);\n", " return mystring;\n", "}\n", "\n", "\n", "function show_questions(json, mydiv) {\n", " console.log('show_questions');\n", " //var mydiv=document.getElementById(myid);\n", " var shuffle_questions = mydiv.dataset.shufflequestions;\n", " var num_questions = mydiv.dataset.numquestions;\n", " var shuffle_answers = mydiv.dataset.shuffleanswers;\n", " var max_width = mydiv.dataset.maxwidth;\n", "\n", " if (num_questions > json.length) {\n", " num_questions = json.length;\n", " }\n", "\n", " var questions;\n", " if ((num_questions < json.length) || (shuffle_questions == \"True\")) {\n", " //console.log(num_questions+\",\"+json.length);\n", " questions = getRandomSubarray(json, num_questions);\n", " } else {\n", " questions = json;\n", " }\n", "\n", " //console.log(\"SQ: \"+shuffle_questions+\", NQ: \" + num_questions + \", SA: \", shuffle_answers);\n", "\n", " // Iterate over questions\n", " questions.forEach((qa, index, array) => {\n", " //console.log(qa.question); \n", "\n", " var id = makeid(8);\n", " //console.log(id);\n", "\n", "\n", " // Create Div to contain question and answers\n", " var iDiv = document.createElement('div');\n", " //iDiv.id = 'quizWrap' + id + index;\n", " iDiv.id = 'quizWrap' + id;\n", " iDiv.className = 'Quiz';\n", " iDiv.setAttribute('data-qnum', index);\n", " iDiv.style.maxWidth =max_width+\"px\";\n", " mydiv.appendChild(iDiv);\n", " // iDiv.innerHTML=qa.question;\n", " \n", " var outerqDiv = document.createElement('div');\n", " outerqDiv.id = \"OuterquizQn\" + id + index;\n", " // Create div to contain question part\n", " var qDiv = document.createElement('div');\n", " qDiv.id = \"quizQn\" + id + index;\n", " \n", " if (qa.question) {\n", " iDiv.append(outerqDiv);\n", "\n", " //qDiv.textContent=qa.question;\n", " qDiv.innerHTML = jaxify(qa.question);\n", " outerqDiv.append(qDiv);\n", " }\n", "\n", " // Create div for code inside question\n", " var codeDiv;\n", " if (\"code\" in qa) {\n", " codeDiv = document.createElement('div');\n", " codeDiv.id = \"code\" + id + index;\n", " codeDiv.className = \"QuizCode\";\n", " var codePre = document.createElement('pre');\n", " codeDiv.append(codePre);\n", " var codeCode = document.createElement('code');\n", " codePre.append(codeCode);\n", " codeCode.innerHTML = qa.code;\n", " outerqDiv.append(codeDiv);\n", " //console.log(codeDiv);\n", " }\n", "\n", "\n", " // Create div to contain answer part\n", " var aDiv = document.createElement('div');\n", " aDiv.id = \"quizAns\" + id + index;\n", " aDiv.className = 'Answer';\n", " iDiv.append(aDiv);\n", "\n", " //console.log(qa.type);\n", "\n", " var num_correct;\n", " if ((qa.type == \"multiple_choice\") || (qa.type == \"many_choice\") ) {\n", " num_correct = make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id);\n", " if (\"answer_cols\" in qa) {\n", " //aDiv.style.gridTemplateColumns = 'auto '.repeat(qa.answer_cols);\n", " aDiv.style.gridTemplateColumns = 'repeat(' + qa.answer_cols + ', 1fr)';\n", " }\n", " } else if (qa.type == \"numeric\") {\n", " //console.log(\"numeric\");\n", " make_numeric(qa, outerqDiv, qDiv, aDiv, id);\n", " }\n", "\n", "\n", " //Make div for feedback\n", " var fb = document.createElement(\"div\");\n", " fb.id = \"fb\" + id;\n", " //fb.style=\"font-size: 20px;text-align:center;\";\n", " fb.className = \"Feedback\";\n", " fb.setAttribute(\"data-answeredcorrect\", 0);\n", " fb.setAttribute(\"data-numcorrect\", num_correct);\n", " iDiv.append(fb);\n", "\n", "\n", " });\n", " var preserveResponses = mydiv.dataset.preserveresponses;\n", " console.log(preserveResponses);\n", " console.log(preserveResponses == \"true\");\n", " if (preserveResponses == \"true\") {\n", " console.log(preserveResponses);\n", " // Create Div to contain record of answers\n", " var iDiv = document.createElement('div');\n", " iDiv.id = 'responses' + mydiv.id;\n", " iDiv.className = 'JCResponses';\n", " // Create a place to store responses as an empty array\n", " iDiv.setAttribute('data-responses', '[]');\n", "\n", " // Dummy Text\n", " iDiv.innerHTML=\"Select your answers and then follow the directions that will appear here.\"\n", " //iDiv.className = 'Quiz';\n", " mydiv.appendChild(iDiv);\n", " }\n", "//console.log(\"At end of show_questions\");\n", " if (typeof MathJax != 'undefined') {\n", " console.log(\"MathJax version\", MathJax.version);\n", " var version = MathJax.version;\n", " setTimeout(function(){\n", " var version = MathJax.version;\n", " console.log('After sleep, MathJax version', version);\n", " if (version[0] == \"2\") {\n", " MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n", " } else if (version[0] == \"3\") {\n", " MathJax.typeset([mydiv]);\n", " }\n", " }, 500);\n", "if (typeof version == 'undefined') {\n", " } else\n", " {\n", " if (version[0] == \"2\") {\n", " MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n", " } else if (version[0] == \"3\") {\n", " MathJax.typeset([mydiv]);\n", " } else {\n", " console.log(\"MathJax not found\");\n", " }\n", " }\n", " }\n", " return false;\n", "}\n", "/* This is to handle asynchrony issues in loading Jupyter notebooks\n", " where the quiz has been previously run. The Javascript was generally\n", " being run before the div was added to the DOM. I tried to do this\n", " more elegantly using Mutation Observer, but I didn't get it to work.\n", "\n", " Someone more knowledgeable could make this better ;-) */\n", "\n", " function try_show() {\n", " if(document.getElementById(\"UWZEHLQDlldB\")) {\n", " show_questions(questionsUWZEHLQDlldB, UWZEHLQDlldB); \n", " } else {\n", " setTimeout(try_show, 200);\n", " }\n", " };\n", " \n", " {\n", " // console.log(element);\n", "\n", " //console.log(\"UWZEHLQDlldB\");\n", " // console.log(document.getElementById(\"UWZEHLQDlldB\"));\n", "\n", " try_show();\n", " }\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from jupyterquiz import display_quiz\n", "display_quiz(\"questions/summary_configuring_git.json\")" ] }, { "cell_type": "code", "execution_count": null, "id": "25dfa441-541c-48dd-9fef-19877052bc55", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.19" } }, "nbformat": 4, "nbformat_minor": 5 }