├── .github
└── workflows
│ └── ExportPluto.yaml
├── My cool notebook.jl
└── README.md
/.github/workflows/ExportPluto.yaml:
--------------------------------------------------------------------------------
1 | name: Export Pluto notebooks
2 | on:
3 | push:
4 | branches:
5 | - main
6 | - master
7 | workflow_dispatch:
8 |
9 | # When two jobs run in parallel, cancel the older ones, to make sure that the website is generated from the most recent commit.
10 | concurrency:
11 | group: pluto-export
12 | cancel-in-progress: true
13 |
14 | # This action needs permission to write the exported HTML file to the gh-pages branch.
15 | permissions:
16 | contents: write
17 | # (all other permission fields default to "none")
18 |
19 | jobs:
20 | build-and-deploy:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Checkout this repository
24 | uses: actions/checkout@v4
25 |
26 | - name: Install Julia
27 | uses: julia-actions/setup-julia@v2
28 | with:
29 | version: "1" # This will automatically pick the latest Julia version
30 |
31 | - name: Cache Julia artifacts & such
32 | uses: julia-actions/cache@v2
33 | with:
34 | cache-registries: "true"
35 |
36 | # We set up a folder that Pluto can use to cache exported notebooks. If the notebook file did not change, then Pluto can take the exported file from cache instead of running the notebook.
37 | - name: Set up notebook state cache
38 | uses: actions/cache@v4
39 | with:
40 | path: pluto_state_cache
41 | key: ${{ runner.os }}-pluto_state_cache-v2-${{ hashFiles('**/Project.toml', '**/Manifest.toml', '.github/workflows/*' ) }}-${{ hashFiles('**/*jl') }}
42 | restore-keys: |
43 | ${{ runner.os }}-pluto_state_cache-v2-${{ hashFiles('**/Project.toml', '**/Manifest.toml', '.github/workflows/*' ) }}
44 |
45 | - name: Ignore notebook state cache in export
46 | run: echo 'pluto_state_cache' >> .gitignore
47 |
48 | - name: Run & export Pluto notebooks
49 | run: |
50 | julia -e 'using Pkg
51 | Pkg.activate(mktempdir())
52 | Pkg.add([
53 | Pkg.PackageSpec(name="PlutoSliderServer", version="1"),
54 | ])
55 |
56 | import PlutoSliderServer
57 |
58 | PlutoSliderServer.github_action(".";
59 | Export_cache_dir="pluto_state_cache",
60 | Export_baked_notebookfile=false,
61 | Export_baked_state=false,
62 | # more parameters can go here
63 | )'
64 |
65 |
66 | - name: Deploy to gh-pages
67 | uses: JamesIves/github-pages-deploy-action@releases/v4
68 | with:
69 | token: ${{ secrets.GITHUB_TOKEN }}
70 | branch: gh-pages
71 | folder: .
72 | single-commit: true
73 |
--------------------------------------------------------------------------------
/My cool notebook.jl:
--------------------------------------------------------------------------------
1 | ### A Pluto.jl notebook ###
2 | # v0.12.18
3 |
4 | using Markdown
5 | using InteractiveUtils
6 |
7 | # ╔═╡ 5b2ee40e-a2b8-11ea-0fef-c35fe6918860
8 | md"""
9 | # The tower of Hanoi
10 |
11 | The tower of hanoi is a famous puzzle.
12 |
13 | 
14 |
15 | The game consists of three rods with disks stacked on top of them. The puzzle will start with all disks in a stack on one of the rods (like in the picture). The goal is to move all the discs to a single stack on different rod.
16 |
17 | To move the disks, you have to follow the following rules:
18 |
19 | * You can move only one disk at a time.
20 | * For each move, you have to take the upper disk from one of the stacks, and place it on top of another stack or empty rod.
21 | * You cannot place a larger disk on top of a smaller disk.
22 |
23 | This notebook will define a Julia implementation of the puzzle. It's up to you to write an algorithm that solves it.
24 | """
25 |
26 | # ╔═╡ 95fbd0d2-a2b9-11ea-0682-fdf783251797
27 | md"""
28 | ## Setting up the game pieces
29 |
30 | What does a Julia implementation look like? We're not really interested in writing code that will manipulate physical disks. Our final goal is a function that will give us a _recipe_ to solve the tower of hanoi, which is just a list of moves to make. Because of that, we can use a lot of abstraction in our implementation, and keep the data structures as simple as possible.
31 |
32 | To start, we have to define some representation of the disks and the stacks. The disks have one important property, which is that they are ordered. We can use integers to represent them.
33 | """
34 |
35 | # ╔═╡ 620d6834-a2ba-11ea-150a-2132bb54e4b3
36 | num_disks = 8
37 |
38 | # ╔═╡ 35ada214-a32c-11ea-0da3-d5d494b28467
39 | md"""(Side note: the number of disks is arbitrary. When testing your function, you may want to set it to 1 or 2 to start.)"""
40 |
41 | # ╔═╡ 7243cc8e-a2ba-11ea-3f29-99356f0cdcf4
42 | all_disks = 1:num_disks
43 |
44 | # ╔═╡ 7e1ba2ac-a2ba-11ea-0134-2f61ed75be18
45 | md"""
46 | A single stack can be represented as an array with all the disks in it. We will list them from top to bottom.
47 | """
48 |
49 | # ╔═╡ 43781a52-a339-11ea-3803-e56e2d08aa83
50 | first_stack = collect(all_disks)
51 |
52 | # ╔═╡ b648ab70-a2ba-11ea-2dcc-55b630e44325
53 | md"""
54 | Now we have to make three of those.
55 | """
56 |
57 | # ╔═╡ 32f26f80-a2bb-11ea-0f2a-3fc631ada63d
58 | starting_stacks = [first_stack, [], []]
59 |
60 | # ╔═╡ e347f1de-a2bb-11ea-06e7-87cca6f2a240
61 | md"""
62 | ## Defining the rules
63 |
64 | Now that we have our "game board", we can implement the rules.
65 |
66 | To start, we make two functions for states. A state of the game is just an array of stacks.
67 |
68 | We will define a function that checks if a state is okay according to the rules. To be legal, all the stacks should be in the correct order, so no larger disks on top of smaller disks.
69 |
70 | Another good thing to check: no disks should have appeared or disappeared since we started!
71 | """
72 |
73 | # ╔═╡ 512fa6d2-a2bd-11ea-3dbe-b935b536967b
74 | function islegal(stacks)
75 | order_correct = all(issorted, stacks)
76 |
77 | # check if we use the same disk set that we started with
78 |
79 | disks_in_state = sort([disk for stack in stacks for disk in stack])
80 | disks_complete = disks_in_state == all_disks
81 |
82 | order_correct && disks_complete
83 | end
84 |
85 | # ╔═╡ c56a5858-a2bd-11ea-1d96-77eaf5e74925
86 | md"""
87 | Another function for states: check if we are done! We can assume that we already checked if the state was legal. So we know that all the disks are there and they are ordered correctly. To check if we are finished, we just need to check if the last stack contains all the disks.
88 | """
89 |
90 | # ╔═╡ d5cc41e8-a2bd-11ea-39c7-b7df8de6ae3e
91 | function iscomplete(stacks)
92 | last(stacks) == all_disks
93 | end
94 |
95 | # ╔═╡ 53374f0e-a2c0-11ea-0c91-97474780721e
96 | md"""
97 | Now the only rules left to implement are the rules for moving disks.
98 |
99 | We could implement this as another check on states, but it's easier to just write a legal `move` function. Your solution will specify moves for the `move` function, so this will be the only way that the stacks are actually manipulated. That way, we are sure that nothing fishy is happening.
100 |
101 | We will make our `move` function so that its input consists of a state of the game, and instructions for what to do. Its output will be the new state of the game.
102 |
103 | So what should those instructions look like? It may seem intuitive to give a _disk_ that should be moved, but that's more than we need. After all, we are only allowed to take the top disk from one stack, and move it to the top of another. So we only have to say which _stacks_ we are moving between.
104 |
105 | (Note that the `move` function is okay with moving a larger disk on top of a smaller disk. We already implemented that restriction in `islegal`.)
106 | """
107 |
108 | # ╔═╡ e915394e-a2c0-11ea-0cd9-1df6fd3c7adf
109 | function move(stacks, source::Int, target::Int)
110 | # check if the from stack if not empty
111 | if isempty(stacks[source])
112 | error("Error: attempted to move disk from empty stack")
113 | end
114 |
115 | new_stacks = deepcopy(stacks)
116 |
117 | disk = popfirst!(new_stacks[source]) # take disk
118 | pushfirst!(new_stacks[target], disk) # put on new stack
119 |
120 | return new_stacks
121 | end
122 |
123 | # ╔═╡ 87b2d164-a2c4-11ea-3095-916628943879
124 | md"""
125 | ## Solving the problem
126 |
127 | We have implemented the game pieces and the rules, so you can start working on your solution.
128 |
129 | To do this, you can fill in the `solve(stacks)` function. This function should give a solution for the given `stacks`, by moving all the disks from stack 1 to stack 3.
130 |
131 | As output, `solve` should give a recipe, that tells us what to do. This recipe should be an array of moves. Each moves is a `(source, target)` tuple, specifying from which stack to which stack you should move.
132 |
133 | For example, it might look like this:
134 | """
135 |
136 | # ╔═╡ 29b410cc-a329-11ea-202a-795b31ce5ad5
137 | function wrong_solution(stacks)::Array{Tuple{Int,Int}}
138 | return [(1, 2), (2, 3), (2, 1), (1, 3)]
139 | end
140 |
141 | # ╔═╡ ea24e778-a32e-11ea-3f11-dbe9d36b1011
142 | md"""
143 | Now you can work on building an actual solution. Some tips:
144 | * `solve(stacks)` can keep track of the board if you want, but it doesn't have to.
145 | * The section below will actually run your moves, which is very useful for checking them.
146 | * If you want, you can change `num_disks` to 1 or 2. That can be a good starting point.
147 | """
148 |
149 | # ╔═╡ 010dbdbc-a2c5-11ea-34c3-837eae17416f
150 | function solve(stacks)::Array{Tuple{Int,Int}}
151 |
152 | # what to do?
153 |
154 | return []
155 | end
156 |
157 | # ╔═╡ 3eb3c4c0-a2c5-11ea-0bcc-c9b52094f660
158 | md"""
159 | ## Checking solutions
160 |
161 | This is where we can check a solution. We start with a function that takes our recipe and runs it.
162 | """
163 |
164 | # ╔═╡ 4709db36-a327-11ea-13a3-bbfb18da84ce
165 | function run_solution(solver::Function, start = starting_stacks)
166 | moves = solver(deepcopy(start)) # apply the solver
167 |
168 | all_states = Array{Any,1}(undef, length(moves) + 1)
169 | all_states[1] = starting_stacks
170 |
171 | for (i, m) in enumerate(moves)
172 | try
173 | all_states[i + 1] = move(all_states[i], m[1], m[2])
174 | catch
175 | all_states[i + 1] = missing
176 | end
177 | end
178 |
179 | return all_states
180 | end
181 |
182 | # ╔═╡ 372824b4-a330-11ea-2f26-7b9a1ad018f1
183 | md"""
184 | You can use this function to see what your solution does.
185 |
186 | If `run_solution` tries to make an impossible move, it will give `missing` from that point onwards. Look at what happens in the `wrong_solution` version and compare it to the moves in `wrong_solution`.
187 | """
188 |
189 | # ╔═╡ d2227b40-a329-11ea-105c-b585d5fcf970
190 | run_solution(wrong_solution)
191 |
192 | # ╔═╡ 9173b174-a327-11ea-3a69-9f7525f2e7b4
193 | run_solution(solve)
194 |
195 | # ╔═╡ bb5088ec-a330-11ea-2c41-6b8b92724b3b
196 | md"""
197 | Now that we have way to run a recipe, we can check if its output is correct. We will check if all the intermediate states are legal and the final state is the finished puzzle.
198 | """
199 |
200 | # ╔═╡ 10fb1c56-a2c5-11ea-2a06-0d8c36bfa138
201 | function check_solution(solver::Function, start = starting_stacks)
202 | try
203 | # run the solution
204 | all_states = run_solution(solver, start)
205 |
206 | # check if each state is legal
207 | all_legal = all(islegal, all_states)
208 |
209 | # check if the final state is is the completed puzzle
210 | complete = (iscomplete ∘ last)(all_states)
211 |
212 | all_legal && complete
213 | catch
214 | # return false if we encountered an error
215 | return false
216 | end
217 | end
218 |
219 | # ╔═╡ 8ea7f944-a329-11ea-22cc-4dbd11ec0610
220 | check_solution(solve)
221 |
222 | # ╔═╡ e54add0a-a330-11ea-2eeb-1d42f552ba38
223 | if check_solution(solve)
224 | if num_disks >= 8
225 | md"""
226 | Congratulations, your solution works!
227 | """
228 | else
229 | md"""
230 | Your solution works for $(num_disks) disks. Change `num_disks` to see if it works for 8 or more.
231 | """
232 | end
233 | else
234 | md"""
235 | The `solve` function doesn't work yet. Keep working on it!
236 | """
237 | end
238 |
239 | # ╔═╡ Cell order:
240 | # ╟─5b2ee40e-a2b8-11ea-0fef-c35fe6918860
241 | # ╟─95fbd0d2-a2b9-11ea-0682-fdf783251797
242 | # ╠═620d6834-a2ba-11ea-150a-2132bb54e4b3
243 | # ╟─35ada214-a32c-11ea-0da3-d5d494b28467
244 | # ╠═7243cc8e-a2ba-11ea-3f29-99356f0cdcf4
245 | # ╟─7e1ba2ac-a2ba-11ea-0134-2f61ed75be18
246 | # ╠═43781a52-a339-11ea-3803-e56e2d08aa83
247 | # ╟─b648ab70-a2ba-11ea-2dcc-55b630e44325
248 | # ╠═32f26f80-a2bb-11ea-0f2a-3fc631ada63d
249 | # ╟─e347f1de-a2bb-11ea-06e7-87cca6f2a240
250 | # ╠═512fa6d2-a2bd-11ea-3dbe-b935b536967b
251 | # ╟─c56a5858-a2bd-11ea-1d96-77eaf5e74925
252 | # ╠═d5cc41e8-a2bd-11ea-39c7-b7df8de6ae3e
253 | # ╟─53374f0e-a2c0-11ea-0c91-97474780721e
254 | # ╠═e915394e-a2c0-11ea-0cd9-1df6fd3c7adf
255 | # ╟─87b2d164-a2c4-11ea-3095-916628943879
256 | # ╠═29b410cc-a329-11ea-202a-795b31ce5ad5
257 | # ╟─ea24e778-a32e-11ea-3f11-dbe9d36b1011
258 | # ╠═010dbdbc-a2c5-11ea-34c3-837eae17416f
259 | # ╟─3eb3c4c0-a2c5-11ea-0bcc-c9b52094f660
260 | # ╠═4709db36-a327-11ea-13a3-bbfb18da84ce
261 | # ╟─372824b4-a330-11ea-2f26-7b9a1ad018f1
262 | # ╠═d2227b40-a329-11ea-105c-b585d5fcf970
263 | # ╠═9173b174-a327-11ea-3a69-9f7525f2e7b4
264 | # ╟─bb5088ec-a330-11ea-2c41-6b8b92724b3b
265 | # ╠═10fb1c56-a2c5-11ea-2a06-0d8c36bfa138
266 | # ╠═8ea7f944-a329-11ea-22cc-4dbd11ec0610
267 | # ╟─e54add0a-a330-11ea-2eeb-1d42f552ba38
268 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # static-export-template
2 |
3 | This is a demo repository containing two [Pluto](https://github.com/fonsp/Pluto.jl) notebooks that are **automatically converted to HTML** by a github action, and published to github pages! 🌝
4 |
5 | See the github pages deployment of this repository:
6 | https://juliapluto.github.io/static-export-template/
7 |
8 | # How to use the template
9 |
10 | ### 👉 Step 1
11 |
12 | Create a GitHub account, and click on ["Use this template"](https://github.com/JuliaPluto/static-export-template/generate). Choose a new name!
13 |
14 |
15 |
16 | You now have your own repository (take a look at the URL), containing a copy of the template files, including the file you are currently reading (`README.md`)!
17 |
18 | ### 👉 Step 2
19 |
20 | Click on **Add files** (or **+**), and then **Upload files**. In the next page, upload your `.jl` notebook files.
21 |
22 |
23 |
24 | Your notebooks will run on github every time that you update the files in this repository. To check the progress, go to the ["Actions" page](./actions) of your repository, you will find the _workflow_ for the last commit.
25 |
26 |
27 |
28 | Wait for the Action to finish running your notebook.
29 |
30 | ### 👉 Step 3
31 |
32 | Go to the ["Settings" page](./settings) of your repository, and go to [the "Pages" section](./settings/pages). For the "Source", choose `gh-pages`. Wait a minute for everything to initialize, and the URL to your web page will be shown!
33 |
34 |
35 |
36 | Don't worry if it doesn't work immediately! It can take a while for the web page to be ready, even after your settings page says it's done. (Github pages says 20 minutes, but it can take even longer.)
37 |
38 | ## Update notebook files
39 |
40 | To update an existing notebook file, simply repeat Step 2 above! (You can also use **Add files** `>` **Upload files** to _update_ upload a file that already exists on the repository.)
41 |
42 | Are you working on this website and planning to make many changes? Then it might be nice to use [GitHub Desktop](https://github.com/apps/desktop), a program on your computer that lets you synchronise your local files with github.com more easily, without having to upload to the website each time. If you are familiar with using the terminal, then you can also use the `git` command to publish changes.
43 |
44 | # Alternative setup: add web pages to an existing repository
45 |
46 | If you already have a github repository with some pluto notebooks in it, you may want to add a web view like the one for this repository. In that case, the steps are slightly different. In this case, I assume that you are familiar with adding files to a repository. If not, follow the steps above.
47 |
48 | ### 👉 Step 1
49 |
50 | From this repository, download [ExportPluto.yaml](./.github/workflows/ExportPluto.yaml).
51 |
52 | Save the file in your own repository, in the same location: make a folder `.github` in the main directory, within that a folder `workflows`, and add the file there, with the name `ExportPluto.yaml`. Commit the new file to your repository.
53 |
54 | *Note: The yaml file states that github should use the github notebooks in the `main` branch or `master` branch of your repository. If you changed the name of your default branch, or you want to use a different branch, change `main` in [line 5](https://github.com/JuliaPluto/static-export-template/blob/main/.github/workflows/ExportPluto.yaml#L5) in the yaml file to the name of your favourite branch.*
55 |
56 | Your notebooks will run on github every time that you update the files in this repository. To check the progress, click on ["Actions"](./actions), you will find the _workflow_ for the last commit.
57 |
58 |
59 |
60 | ### 👉 Step 2
61 |
62 | Go to the ["Settings" page](./settings) of your repository, and go to [the "Pages" section](./settings/pages). For the "Source", choose `gh-pages`. Wait a minute for everything to initialize, and the URL to your web page will be shown!
63 |
64 |
65 |
66 | Don't worry if it doesn't work immediately! It can take a while for the web page to be ready, even after your settings page says it's done. (Github pages says 20 minutes, but it can take even longer.)
67 |
68 |
69 | # 💡Tips
70 |
71 | ### Julia Packages
72 |
73 | Because Pluto has a [built-in package manager](https://github.com/fonsp/Pluto.jl/wiki/%F0%9F%8E%81-Package-management), packages will automatically work on the website!
74 |
75 | ### Default files
76 |
77 | This files `README.md` and `My cool notebook.jl` are example files, and you can safely edit or delete them! After you have set up your repository, you can remove these setup instructions from the `README.md` file, and write a description of your project.
78 |
79 | ### Not working?
80 |
81 | If your website is not automatically updating, then go to the **"Actions"** page of your repository, and take a look at the latest logs. If they show a ❌ symbol, then something went wrong. Click on it, and carefully read the error message. If you get stuck, be sure to ask for help: open an issue at https://github.com/JuliaPluto/static-export-template/issues/new and send us a link to your repository.
82 |
83 | ### Homepage
84 |
85 | If you go to the (GitHub Pages) URL of repository, you will see a small index of the notebooks in this repository. You can customize this page, two options are:
86 |
87 | - Create your own `index.html` or `index.md` file, it will be used as the homepage.
88 | - Rename one of your notebooks to `index.jl`, and it will be the default notebook!
89 |
90 | # License
91 |
92 | This *template* repository is [Unlicensed](https://unlicense.org), which means that you can do anything you want with it! When you use this template, you are also free to remove this license message.
93 |
94 |
95 | View license text
96 | (This license applies to the template https://github.com/JuliaPluto/static-export-template, not necessarily to the user of the template.)
97 |
98 | ```
99 | This is free and unencumbered software released into the public domain.
100 |
101 | Anyone is free to copy, modify, publish, use, compile, sell, or
102 | distribute this software, either in source code form or as a compiled
103 | binary, for any purpose, commercial or non-commercial, and by any
104 | means.
105 |
106 | In jurisdictions that recognize copyright laws, the author or authors
107 | of this software dedicate any and all copyright interest in the
108 | software to the public domain. We make this dedication for the benefit
109 | of the public at large and to the detriment of our heirs and
110 | successors. We intend this dedication to be an overt act of
111 | relinquishment in perpetuity of all present and future rights to this
112 | software under copyright law.
113 |
114 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
115 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
116 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
117 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
118 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
119 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
120 | OTHER DEALINGS IN THE SOFTWARE.
121 |
122 | For more information, please refer to
123 | ```
124 |
125 |
--------------------------------------------------------------------------------