├── .credo.exs ├── .formatter.exs ├── .github ├── dependabot.yml └── workflows │ └── quality.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config └── config.exs ├── demo.gif ├── lib ├── scenic_live_reload.ex ├── scenic_live_reload │ └── private │ │ └── fetch_scenic_info.ex └── scenic_live_reload_application.ex ├── mix.exs ├── mix.lock └── test ├── scenic_live_reload_test.exs └── test_helper.exs /.credo.exs: -------------------------------------------------------------------------------- 1 | # This file contains the configuration for Credo and you are probably reading 2 | # this after creating it with `mix credo.gen.config`. 3 | # 4 | # If you find anything wrong or unclear in this file, please report an 5 | # issue on GitHub: https://github.com/rrrene/credo/issues 6 | # 7 | %{ 8 | # 9 | # You can have as many configs as you like in the `configs:` field. 10 | configs: [ 11 | %{ 12 | # 13 | # Run any config using `mix credo -C `. If no config name is given 14 | # "default" is used. 15 | # 16 | name: "default", 17 | # 18 | # These are the files included in the analysis: 19 | files: %{ 20 | # 21 | # You can give explicit globs or simply directories. 22 | # In the latter case `**/*.{ex,exs}` will be used. 23 | # 24 | included: [ 25 | "lib/", 26 | "src/", 27 | "test/", 28 | "web/", 29 | "apps/*/lib/", 30 | "apps/*/src/", 31 | "apps/*/test/", 32 | "apps/*/web/" 33 | ], 34 | excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] 35 | }, 36 | # 37 | # Load and configure plugins here: 38 | # 39 | plugins: [], 40 | # 41 | # If you create your own checks, you must specify the source files for 42 | # them here, so they can be loaded by Credo before running the analysis. 43 | # 44 | requires: [], 45 | # 46 | # If you want to enforce a style guide and need a more traditional linting 47 | # experience, you can change `strict` to `true` below: 48 | # 49 | strict: false, 50 | # 51 | # To modify the timeout for parsing files, change this value: 52 | # 53 | parse_timeout: 5000, 54 | # 55 | # If you want to use uncolored output by default, you can change `color` 56 | # to `false` below: 57 | # 58 | color: true, 59 | # 60 | # You can customize the parameters of any check by adding a second element 61 | # to the tuple. 62 | # 63 | # To disable a check put `false` as second element: 64 | # 65 | # {Credo.Check.Design.DuplicatedCode, false} 66 | # 67 | checks: %{ 68 | enabled: [ 69 | # 70 | ## Consistency Checks 71 | # 72 | {Credo.Check.Consistency.ExceptionNames, []}, 73 | {Credo.Check.Consistency.LineEndings, []}, 74 | {Credo.Check.Consistency.ParameterPatternMatching, []}, 75 | {Credo.Check.Consistency.SpaceAroundOperators, []}, 76 | {Credo.Check.Consistency.SpaceInParentheses, []}, 77 | {Credo.Check.Consistency.TabsOrSpaces, []}, 78 | 79 | # 80 | ## Design Checks 81 | # 82 | # You can customize the priority of any check 83 | # Priority values are: `low, normal, high, higher` 84 | # 85 | {Credo.Check.Design.AliasUsage, 86 | [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, 87 | # You can also customize the exit_status of each check. 88 | # If you don't want TODO comments to cause `mix credo` to fail, just 89 | # set this value to 0 (zero). 90 | # 91 | {Credo.Check.Design.TagTODO, [exit_status: 2]}, 92 | {Credo.Check.Design.TagFIXME, []}, 93 | 94 | # 95 | ## Readability Checks 96 | # 97 | {Credo.Check.Readability.AliasOrder, []}, 98 | {Credo.Check.Readability.FunctionNames, []}, 99 | {Credo.Check.Readability.LargeNumbers, []}, 100 | {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, 101 | {Credo.Check.Readability.ModuleAttributeNames, []}, 102 | {Credo.Check.Readability.ModuleDoc, []}, 103 | {Credo.Check.Readability.ModuleNames, []}, 104 | {Credo.Check.Readability.ParenthesesInCondition, []}, 105 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, 106 | {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, 107 | {Credo.Check.Readability.PredicateFunctionNames, []}, 108 | {Credo.Check.Readability.PreferImplicitTry, []}, 109 | {Credo.Check.Readability.RedundantBlankLines, []}, 110 | {Credo.Check.Readability.Semicolons, []}, 111 | {Credo.Check.Readability.SpaceAfterCommas, []}, 112 | {Credo.Check.Readability.StringSigils, []}, 113 | {Credo.Check.Readability.TrailingBlankLine, []}, 114 | {Credo.Check.Readability.TrailingWhiteSpace, []}, 115 | {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, 116 | {Credo.Check.Readability.VariableNames, []}, 117 | {Credo.Check.Readability.WithSingleClause, []}, 118 | 119 | # 120 | ## Refactoring Opportunities 121 | # 122 | {Credo.Check.Refactor.Apply, []}, 123 | {Credo.Check.Refactor.CondStatements, []}, 124 | {Credo.Check.Refactor.CyclomaticComplexity, []}, 125 | {Credo.Check.Refactor.FunctionArity, []}, 126 | {Credo.Check.Refactor.LongQuoteBlocks, []}, 127 | {Credo.Check.Refactor.MatchInCondition, []}, 128 | {Credo.Check.Refactor.MapJoin, []}, 129 | {Credo.Check.Refactor.NegatedConditionsInUnless, []}, 130 | {Credo.Check.Refactor.NegatedConditionsWithElse, []}, 131 | {Credo.Check.Refactor.Nesting, []}, 132 | {Credo.Check.Refactor.UnlessWithElse, []}, 133 | {Credo.Check.Refactor.WithClauses, []}, 134 | {Credo.Check.Refactor.FilterFilter, []}, 135 | {Credo.Check.Refactor.RejectReject, []}, 136 | {Credo.Check.Refactor.RedundantWithClauseResult, []}, 137 | 138 | # 139 | ## Warnings 140 | # 141 | {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, 142 | {Credo.Check.Warning.BoolOperationOnSameValues, []}, 143 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, 144 | {Credo.Check.Warning.IExPry, []}, 145 | {Credo.Check.Warning.IoInspect, []}, 146 | {Credo.Check.Warning.OperationOnSameValues, []}, 147 | {Credo.Check.Warning.OperationWithConstantResult, []}, 148 | {Credo.Check.Warning.RaiseInsideRescue, []}, 149 | {Credo.Check.Warning.SpecWithStruct, []}, 150 | {Credo.Check.Warning.WrongTestFileExtension, []}, 151 | {Credo.Check.Warning.UnusedEnumOperation, []}, 152 | {Credo.Check.Warning.UnusedFileOperation, []}, 153 | {Credo.Check.Warning.UnusedKeywordOperation, []}, 154 | {Credo.Check.Warning.UnusedListOperation, []}, 155 | {Credo.Check.Warning.UnusedPathOperation, []}, 156 | {Credo.Check.Warning.UnusedRegexOperation, []}, 157 | {Credo.Check.Warning.UnusedStringOperation, []}, 158 | {Credo.Check.Warning.UnusedTupleOperation, []}, 159 | {Credo.Check.Warning.UnsafeExec, []}, 160 | 161 | # 162 | ## Enabled 163 | # 164 | {Credo.Check.Readability.StrictModuleLayout, 165 | priority: :normal, 166 | order: [:shortdoc, :moduledoc, :use, :import, :require, :alias, :behaviour], 167 | ignore: [:module]}, 168 | ], 169 | disabled: [ 170 | # 171 | # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) 172 | 173 | # 174 | # Controversial and experimental checks (opt-in, just move the check to `:enabled` 175 | # and be sure to use `mix credo --strict` to see low priority checks) 176 | # 177 | {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, 178 | {Credo.Check.Consistency.UnusedVariableNames, []}, 179 | {Credo.Check.Design.DuplicatedCode, []}, 180 | {Credo.Check.Design.SkipTestWithoutComment, []}, 181 | {Credo.Check.Readability.AliasAs, []}, 182 | {Credo.Check.Readability.BlockPipe, []}, 183 | {Credo.Check.Readability.ImplTrue, []}, 184 | {Credo.Check.Readability.MultiAlias, []}, 185 | {Credo.Check.Readability.NestedFunctionCalls, []}, 186 | {Credo.Check.Readability.SeparateAliasRequire, []}, 187 | {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, 188 | {Credo.Check.Readability.SinglePipe, []}, 189 | {Credo.Check.Readability.Specs, []}, 190 | {Credo.Check.Readability.WithCustomTaggedTuple, []}, 191 | {Credo.Check.Refactor.ABCSize, []}, 192 | {Credo.Check.Refactor.AppendSingleItem, []}, 193 | {Credo.Check.Refactor.DoubleBooleanNegation, []}, 194 | {Credo.Check.Refactor.FilterReject, []}, 195 | {Credo.Check.Refactor.IoPuts, []}, 196 | {Credo.Check.Refactor.MapMap, []}, 197 | {Credo.Check.Refactor.ModuleDependencies, []}, 198 | {Credo.Check.Refactor.NegatedIsNil, []}, 199 | {Credo.Check.Refactor.PipeChainStart, []}, 200 | {Credo.Check.Refactor.RejectFilter, []}, 201 | {Credo.Check.Refactor.VariableRebinding, []}, 202 | {Credo.Check.Warning.LazyLogging, []}, 203 | {Credo.Check.Warning.LeakyEnvironment, []}, 204 | {Credo.Check.Warning.MapGetUnsafePass, []}, 205 | {Credo.Check.Warning.MixEnv, []}, 206 | {Credo.Check.Warning.UnsafeToAtom, []} 207 | 208 | # {Credo.Check.Refactor.MapInto, []}, 209 | 210 | # 211 | # Custom checks can be created using `mix credo.gen.check`. 212 | # 213 | ] 214 | } 215 | } 216 | ] 217 | } 218 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: mix 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "15:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: ex_doc 11 | versions: 12 | - 0.23.0 13 | - 0.24.0 14 | - 0.24.1 15 | - dependency-name: scenic 16 | versions: 17 | - 0.10.3 18 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | name: Quality 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | quality: 11 | name: Quality 12 | runs-on: ubuntu-latest 13 | env: 14 | MIX_ENV: dev 15 | strategy: 16 | matrix: 17 | elixir: ["1.13.4"] 18 | otp: ["24.3.4"] 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | 24 | - name: Setup Elixir 25 | uses: erlef/setup-beam@v1 26 | with: 27 | otp-version: ${{matrix.otp}} 28 | elixir-version: ${{matrix.elixir}} 29 | 30 | - run: mix deps.get 31 | - run: mix deps.compile 32 | - run: mix compile --warnings-as-errors 33 | - run: mix format --check-formatted 34 | - run: mix credo 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | scenic_live_reload-*.tar 24 | 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 5 | 6 | ## [Unreleased] 7 | 8 | ## [0.3.1] - 2022-09-05 9 | 10 | * Allow both `reload_current_scene` and `reload_current_scenes` 11 | 12 | ## [0.3.0] - 2022-09-05 13 | 14 | * Updated to work with Scenic 0.11 15 | 16 | Breaking changes: 17 | * ScenicLiveReload no longer needs to be added to the list of children in your application 18 | * Remove ScenicLiveReload from the list of children in your application 19 | * Change the configuration to: 20 | 21 | ```elixir 22 | case Mix.env() do 23 | :dev -> 24 | config :exsync, 25 | reload_timeout: 150, 26 | reload_callback: {ScenicLiveReload, :reload_current_scenes, []} 27 | 28 | _ -> 29 | nil 30 | end 31 | ``` 32 | 33 | ## [0.2.2] - 2021-06-13 34 | 35 | * Tested with scenic 0.10.5 36 | 37 | ## [0.2.1] - 2021-06-13 38 | 39 | * More restrictive scenic version 40 | * It is possible that ScenicLiveReload will break on the next Scenic version: https://github.com/boydm/scenic/issues/210 41 | 42 | ## [0.2.0] - 2019-11-21 43 | 44 | Breaking changes: 45 | * Now ScenicLiveReload needs to be added to your supervision tree, passing in 46 | the viewport 47 | * This is needed for ScenicLiveReload to be more discerning about which 48 | process it kills to reset the state of your scene 49 | * Previously sometimes your scene would fail to reload because one of it's 50 | child scenes was reloaded first 51 | 52 | To upgrade add `ScenicLiveReload` to your supervision tree, passing in your viewport configuration. So in a file like `my_app/application.ex`: 53 | 54 | ``` 55 | main_viewport_config = Application.get_env(:pomodoro, :viewport) 56 | 57 | children = [ 58 | # other children 59 | {ScenicLiveReload, viewports: [main_viewport_config]} 60 | ] 61 | ``` 62 | 63 | An example of upgrading can be seen in the official demo application: https://github.com/axelson/scenic_live_reload_demo/pull/1 64 | 65 | ## [0.1.0] - 2019-09-03 66 | 67 | Initial release 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScenicLiveReload 2 | 3 | A live reloader for Scenic. Once configured, when you edit a file the current 4 | root scene will be killed, when it is restarted it will use any new code 5 | currently in the running beam instance. 6 | 7 | How it works: 8 | * Uses [exsync](https://github.com/axelson/exsync) to watch the file system 9 | * When you edit a file with your editor, exsync recompiles and reloads that beam file 10 | * `exsync` notifies `scenic_live_reload` that files were reloaded 11 | * `scenic_live_reload` kills the currently displayed root scene 12 | * The OTP Supervisor for that scene restarts the scene with the updated code 13 | 14 | # Demo 15 | 16 | In this demo I replace the text "World" with "Scenic", then change the color of 17 | the text to purple. Next I change the shape of the bezier line. 18 | 19 | [![Screencast Demo](./demo.gif)](https://raw.githubusercontent.com/axelson/scenic-starter-pack/master/demo.gif) 20 | 21 | Note: the code running in the demo can be found at https://github.com/axelson/scenic_live_reload_demo 22 | 23 | See also: [scenic-starter-pack](https://github.com/axelson/scenic-starter-pack) 24 | 25 | # Installation 26 | 27 | Add `scenic_live_reload` to your list of dependencies in `mix.exs`: 28 | 29 | ```elixir 30 | def deps do 31 | [ 32 | ... 33 | {:scenic_live_reload, "~> 0.3", only: :dev}, 34 | ] 35 | end 36 | ``` 37 | 38 | Add this configuration to your `config.exs`: 39 | ``` 40 | case Mix.env() do 41 | :dev -> 42 | config :exsync, 43 | reload_timeout: 150, 44 | reload_callback: {ScenicLiveReload, :reload_current_scenes, []} 45 | 46 | _ -> 47 | nil 48 | end 49 | ``` 50 | 51 | Then start your application with `mix scenic.run` (or `iex -S mix`), edit a file with your editor, and then you should see your change reflected. 52 | 53 | # Development 54 | 55 | NOTE: This currently relies on a private scenic api to get the root scene which may change in future Scenic releases 56 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | import Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # third-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :scenic_live_reload, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:scenic_live_reload, :key) 18 | # 19 | # You can also configure a third-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env()}.exs" 31 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelson/scenic_live_reload/404e89478d0698feb2aac22a2e2b44dd0ccd8030/demo.gif -------------------------------------------------------------------------------- /lib/scenic_live_reload.ex: -------------------------------------------------------------------------------- 1 | defmodule ScenicLiveReload do 2 | @moduledoc """ 3 | A simple, generic code reloader for Scenic Scenes 4 | """ 5 | use GenServer 6 | require Logger 7 | 8 | defmodule State do 9 | @moduledoc false 10 | defstruct [] 11 | end 12 | 13 | def start_link(state, name \\ __MODULE__) do 14 | GenServer.start_link(__MODULE__, state, name: name) 15 | end 16 | 17 | @impl GenServer 18 | def init(_opts) do 19 | Logger.debug("SceneReloader running #{inspect(self())}") 20 | 21 | state = %State{} 22 | 23 | {:ok, state} 24 | end 25 | 26 | def reload_current_scene, do: reload_current_scenes() 27 | 28 | def reload_current_scenes do 29 | GenServer.call(__MODULE__, :reload_current_scenes) 30 | end 31 | 32 | @impl GenServer 33 | def handle_call(:reload_current_scenes, _, state) do 34 | Logger.info("ScenicLiveReload reloading current scenes!") 35 | do_reload_current_scenes() 36 | 37 | {:reply, nil, state} 38 | end 39 | 40 | def do_reload_current_scenes do 41 | ScenicLiveReload.Private.FetchScenicInfo.view_port_pids() 42 | |> Enum.each(fn pid -> 43 | {:ok, view_port} = Scenic.ViewPort.info(pid) 44 | {scene, params} = ScenicLiveReload.Private.FetchScenicInfo.get_current_scene(view_port) 45 | Scenic.ViewPort.set_root(view_port, scene, params) 46 | end) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/scenic_live_reload/private/fetch_scenic_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ScenicLiveReload.Private.FetchScenicInfo do 2 | @moduledoc """ 3 | WARNING: Relies on private scenic API's 4 | 5 | Allows you to get the PID's of the currently running viewports. There is a 6 | proposal to create a private api for this functionality that has been 7 | discussed at: 8 | https://github.com/boydm/scenic_new/pull/19 9 | 10 | Also allows getting information about the current scene 11 | """ 12 | 13 | def view_port_pids do 14 | # WARNING: the `:scenic_viewports` supervisor is an internal Scenic 15 | # implementation detail 16 | children = DynamicSupervisor.which_children(:scenic_viewports) 17 | 18 | Enum.map(children, fn child -> 19 | {_, root_scene_pid, _, _} = child 20 | root_scene_pid 21 | end) 22 | end 23 | 24 | def get_current_scene(%Scenic.ViewPort{} = view_port) do 25 | # WARNING: Private API 26 | %{scene: {scene, params}} = :sys.get_state(view_port.pid) 27 | {scene, params} 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/scenic_live_reload_application.ex: -------------------------------------------------------------------------------- 1 | defmodule ScenicLiveReloadApplication do 2 | @moduledoc false 3 | 4 | use Application 5 | 6 | @impl true 7 | def start(_type, _args) do 8 | children = [ 9 | ScenicLiveReload 10 | ] 11 | 12 | opts = [strategy: :one_for_one, name: :scenic_live_reload_supervisor] 13 | Supervisor.start_link(children, opts) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ScenicLiveReload.MixProject do 2 | use Mix.Project 3 | 4 | @app :scenic_live_reload 5 | @version "0.3.1" 6 | @github_url "https://github.com/axelson/scenic_live_reload/" 7 | 8 | def project do 9 | [ 10 | app: @app, 11 | version: @version, 12 | description: description(), 13 | package: package(), 14 | elixir: "~> 1.8", 15 | start_permanent: Mix.env() == :prod, 16 | deps: deps(), 17 | name: "Scenic Live Reload", 18 | docs: [ 19 | canonical: "https://hexdocs.pm/scenic_live_reload" 20 | ], 21 | source_url: @github_url, 22 | homepage_url: @github_url 23 | ] 24 | end 25 | 26 | def package do 27 | [ 28 | name: @app, 29 | files: ["lib", "mix.exs", "README*", "LICENSE*", "CHANGELOG.md"], 30 | maintainers: ["Jason Axelson"], 31 | licenses: ["Apache-2.0"], 32 | links: %{"GitHub" => @github_url} 33 | ] 34 | end 35 | 36 | def description do 37 | """ 38 | A library that allows you to live-reload your Scenic code in development, 39 | utilizes exsync. 40 | """ 41 | end 42 | 43 | def application do 44 | [ 45 | mod: {ScenicLiveReloadApplication, []}, 46 | extra_applications: [:logger] 47 | ] 48 | end 49 | 50 | defp deps do 51 | [ 52 | {:credo, "~> 1.6", only: :dev}, 53 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, 54 | dep(:exsync, :hex), 55 | {:scenic, "~> 0.11"} 56 | ] 57 | end 58 | 59 | defp dep(:exsync, :hex), do: {:exsync, "~> 0.2"} 60 | defp dep(:exsync, :github), do: {:exsync, github: "axelson/exsync"} 61 | defp dep(:exsync, :path), do: {:exsync, path: "../forks/exsync"} 62 | end 63 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, 3 | "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, 4 | "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, 5 | "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"}, 6 | "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, 7 | "ex_doc": {:hex, :ex_doc, "0.28.5", "3e52a6d2130ce74d096859e477b97080c156d0926701c13870a4e1f752363279", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d2c4b07133113e9aa3e9ba27efb9088ba900e9e51caa383919676afdf09ab181"}, 8 | "ex_image_info": {:hex, :ex_image_info, "0.2.4", "610002acba43520a9b1cf1421d55812bde5b8a8aeaf1fe7b1f8823e84e762adb", [:mix], [], "hexpm", "fd1a7e02664e3b14dfd3b231d22fdd48bd3dd694c4773e6272b3a6228f1106bc"}, 9 | "exsync": {:hex, :exsync, "0.2.4", "5cdc824553e0f4c4bf60018a9a6bbd5d3b51f93ef8401a0d8545f93127281d03", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f7622d8bb98abbe473aa066ae46f91afdf7a5346b8b89728404f7189d2e80896"}, 10 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, 11 | "font_metrics": {:hex, :font_metrics, "0.5.1", "10ce0b8b1bf092a2d3d307e05a7c433787ae8ca7cd1f3cf959995a628809a994", [:mix], [{:nimble_options, "~> 0.3", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "192e4288772839ae4dadccb0f5b1d5c89b73a0c3961ccea14b6181fdcd535e54"}, 12 | "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, 13 | "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, 14 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, 15 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 16 | "msgpax": {:hex, :msgpax, "2.3.0", "14f52ad249a3f77b5e2d59f6143e6c18a6e74f34666989e22bac0a465f9835cc", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "65c36846a62ed5615baf7d7d47babb6541313a6c0b6d2ff19354bd518f52df7e"}, 17 | "nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"}, 18 | "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, 19 | "scenic": {:hex, :scenic, "0.11.0", "2762b6fcf54c421d886163eb0621fac731150f206a8206591bf2200b8b6bb2ea", [:make, :mix], [{:elixir_make, "~> 0.6.2", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:ex_image_info, "~> 0.2.4", [hex: :ex_image_info, repo: "hexpm", optional: false]}, {:font_metrics, "~> 0.5.0", [hex: :font_metrics, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.4 or ~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:truetype_metrics, "~> 0.6", [hex: :truetype_metrics, repo: "hexpm", optional: false]}], "hexpm", "7f5b1e51cec7dbf9056ac75a6c51a373a8f426bd97cc05c1104d30297566db1e"}, 20 | "truetype_metrics": {:hex, :truetype_metrics, "0.6.1", "9119a04dc269dd8f63e85e12e4098f711cb7c5204a420f4896f40667b9e064f6", [:mix], [{:font_metrics, "~> 0.5", [hex: :font_metrics, repo: "hexpm", optional: false]}], "hexpm", "5711d4a3e4fc92eb073326fbe54208925d35168dc9b288c331ee666a8a84759b"}, 21 | } 22 | -------------------------------------------------------------------------------- /test/scenic_live_reload_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ScenicLiveReloadTest do 2 | use ExUnit.Case 3 | doctest ScenicLiveReload 4 | 5 | test "README.md version is up to date" do 6 | app = :scenic_live_reload 7 | app_version = Application.spec(app, :vsn) |> to_string() 8 | readme = File.read!("README.md") 9 | [_, readme_version] = Regex.run(~r/{:#{app}, "(.+)", only: :dev}/, readme) 10 | assert Version.match?(app_version, readme_version) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------