├── project ├── build.properties ├── project │ └── plugins.sbt └── plugins.sbt ├── .scalafmt.conf ├── sourcepos └── src │ ├── main │ ├── scala │ │ └── SourcePos.scala │ ├── scala-2 │ │ └── SourcePosPlatform.scala │ └── scala-3 │ │ └── SourcPosPlatform.scala │ └── test │ └── scala │ ├── Example.scala │ └── Test.scala ├── .gitignore ├── LICENSE ├── README.md └── .github └── workflows ├── clean.yml └── ci.yml /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.6 2 | -------------------------------------------------------------------------------- /project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.3") 2 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.6.1" 2 | runner.dialect = Scala213Source3 3 | project.includePaths = [] # disables formatting 4 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.3") 2 | addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.0") 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1") 4 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.8") 5 | -------------------------------------------------------------------------------- /sourcepos/src/main/scala/SourcePos.scala: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2021 by Rob Norris 2 | // This software is licensed under the MIT License (MIT). 3 | // For more information see LICENSE or https://opensource.org/licenses/MIT 4 | 5 | package org.tpolecat.sourcepos 6 | 7 | final case class SourcePos(file: String, line: Int) { 8 | override def toString = 9 | s"$file:$line" 10 | } 11 | 12 | object SourcePos extends SourcePosPlatform -------------------------------------------------------------------------------- /sourcepos/src/test/scala/Example.scala: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2021 by Rob Norris 2 | // This software is licensed under the MIT License (MIT). 3 | // For more information see LICENSE or https://opensource.org/licenses/MIT 4 | 5 | import org.tpolecat.sourcepos._ 6 | 7 | object Example { 8 | 9 | def method(n: Int)(implicit sp: SourcePos): String = 10 | s"You called me with $n from $sp" 11 | 12 | def main(args: Array[String]): Unit = 13 | println(method(42)) 14 | 15 | } -------------------------------------------------------------------------------- /sourcepos/src/test/scala/Test.scala: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2021 by Rob Norris 2 | // This software is licensed under the MIT License (MIT). 3 | // For more information see LICENSE or https://opensource.org/licenses/MIT 4 | 5 | package test 6 | 7 | import org.tpolecat.sourcepos._ 8 | 9 | class Test extends munit.FunSuite { 10 | 11 | test("pos") { 12 | val pos = implicitly[SourcePos] 13 | assert(clue(pos.file).endsWith("src/test/scala/Test.scala")) 14 | assertEquals(pos.line, 12) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /sourcepos/src/main/scala-2/SourcePosPlatform.scala: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2021 by Rob Norris 2 | // This software is licensed under the MIT License (MIT). 3 | // For more information see LICENSE or https://opensource.org/licenses/MIT 4 | 5 | package org.tpolecat.sourcepos 6 | 7 | import scala.reflect.macros.blackbox 8 | 9 | trait SourcePosPlatform { 10 | implicit def instance: SourcePos = 11 | macro SourcePosPlatform.sourcePos_impl 12 | } 13 | 14 | object SourcePosPlatform { 15 | def sourcePos_impl(c: blackbox.Context): c.Tree = { 16 | import c.universe._ 17 | val file = c.enclosingPosition.source.path 18 | val line = c.enclosingPosition.line 19 | q"_root_.org.tpolecat.sourcepos.SourcePos($file, $line)" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sourcepos/src/main/scala-3/SourcPosPlatform.scala: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-2021 by Rob Norris 2 | // This software is licensed under the MIT License (MIT). 3 | // For more information see LICENSE or https://opensource.org/licenses/MIT 4 | 5 | package org.tpolecat.sourcepos 6 | 7 | import scala.quoted._ 8 | 9 | trait SourcePosPlatform { 10 | 11 | implicit inline def instance: SourcePos = 12 | ${SourcePosPlatform.sourcePos_impl} 13 | 14 | } 15 | 16 | object SourcePosPlatform { 17 | 18 | def sourcePos_impl(using ctx: Quotes): Expr[SourcePos] = { 19 | val rootPosition = ctx.reflect.Position.ofMacroExpansion 20 | val file = Expr(rootPosition.sourceFile.path) 21 | val line = Expr(rootPosition.startLine + 1) 22 | '{SourcePos($file, $line)} 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # these are moved into the doc project by the build 5 | modules/docs/src/main/tut/changelog.md 6 | modules/docs/src/main/tut/license.md 7 | 8 | # sbt specific 9 | dist/* 10 | target/ 11 | lib_managed/ 12 | src_managed/ 13 | project/boot/ 14 | project/plugins/project/ 15 | project/hydra.sbt 16 | 17 | # Scala-IDE specific 18 | .scala_dependencies 19 | .cache 20 | .classpath 21 | .project 22 | .worksheet/ 23 | .settings/ 24 | 25 | # OS X 26 | .DS_Store 27 | 28 | # Ctags 29 | .tags 30 | 31 | # ENSIME 32 | .ensime 33 | .ensime_cache/ 34 | 35 | # IntelliJ 36 | .idea/ 37 | 38 | # Mill 39 | out/ 40 | 41 | # Bloop/Metals/vscode 42 | .bloop/ 43 | .metals/ 44 | .vscode/ 45 | metals.sbt 46 | 47 | # Transient workspace 48 | modules/docs/src/main/scala/ 49 | *.worksheet.sc 50 | 51 | # bsp 52 | .bsp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 by Rob Norris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SourcePos 2 | 3 | This is a Scala micro-library that provides: 4 | 5 | - a `SourcePos` class that specifies a source file and line number, and 6 | - an implicit macro that forges a `SourcePos` at the call site if none is available. 7 | 8 | SourcePos is compiled for Scala **2.12**, **2.13**, and **3.0** (see release notes for exact versions). 9 | 10 | 11 | ```scala 12 | libraryDependencies += "org.tpolecat" %% "sourcepos" % 13 | ``` 14 | 15 | #### Example 16 | 17 | If you want to know where a method call originated, demand an implicit `SourcePos`. It has fields for both `file` and `line`, and a `toString` that prints them as `:`, which most editors will hyperlink for you. 18 | 19 | ```scala 20 | import org.tpolecat.sourcepos._ 21 | 22 | object Example { 23 | 24 | def method(n: Int)(implicit sp: SourcePos): String = 25 | s"You called me with $n from $sp" 26 | 27 | def main(args: Array[String]): Unit = 28 | println(method(42)) 29 | 30 | } 31 | ``` 32 | 33 | Running this program on my computer yields: 34 | 35 | ``` 36 | You called me with 42 from /Users/rnorris/Scala/SourcePos/src/test/scala/Example.scala:9 37 | ``` 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done 60 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['**', '!update/**', '!pr/**'] 13 | push: 14 | branches: ['**', '!update/**', '!pr/**'] 15 | tags: [v*] 16 | 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | 21 | concurrency: 22 | group: ${{ github.workflow }} @ ${{ github.ref }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | build: 27 | name: Test 28 | strategy: 29 | matrix: 30 | os: [ubuntu-22.04] 31 | scala: [2.12, 2.13, 3] 32 | java: [temurin@8] 33 | project: [rootJS, rootJVM, rootNative] 34 | runs-on: ${{ matrix.os }} 35 | timeout-minutes: 60 36 | steps: 37 | - name: Checkout current branch (full) 38 | uses: actions/checkout@v4 39 | with: 40 | fetch-depth: 0 41 | 42 | - name: Setup sbt 43 | uses: sbt/setup-sbt@v1 44 | 45 | - name: Setup Java (temurin@8) 46 | id: setup-java-temurin-8 47 | if: matrix.java == 'temurin@8' 48 | uses: actions/setup-java@v4 49 | with: 50 | distribution: temurin 51 | java-version: 8 52 | cache: sbt 53 | 54 | - name: sbt update 55 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' 56 | run: sbt +update 57 | 58 | - name: Check that workflows are up to date 59 | run: sbt githubWorkflowCheck 60 | 61 | - name: Check headers and formatting 62 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' 63 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck 64 | 65 | - name: scalaJSLink 66 | if: matrix.project == 'rootJS' 67 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult 68 | 69 | - name: nativeLink 70 | if: matrix.project == 'rootNative' 71 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/nativeLink 72 | 73 | - name: Test 74 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test 75 | 76 | - name: Check binary compatibility 77 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' 78 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues 79 | 80 | - name: Generate API documentation 81 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' 82 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc 83 | 84 | - name: Make target directories 85 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 86 | run: mkdir -p sourcepos/.js/target sourcepos/.native/target sourcepos/.jvm/target project/target 87 | 88 | - name: Compress target directories 89 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 90 | run: tar cf targets.tar sourcepos/.js/target sourcepos/.native/target sourcepos/.jvm/target project/target 91 | 92 | - name: Upload target directories 93 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 94 | uses: actions/upload-artifact@v4 95 | with: 96 | name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}-${{ matrix.project }} 97 | path: targets.tar 98 | 99 | publish: 100 | name: Publish Artifacts 101 | needs: [build] 102 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 103 | strategy: 104 | matrix: 105 | os: [ubuntu-22.04] 106 | java: [temurin@8] 107 | runs-on: ${{ matrix.os }} 108 | steps: 109 | - name: Checkout current branch (full) 110 | uses: actions/checkout@v4 111 | with: 112 | fetch-depth: 0 113 | 114 | - name: Setup sbt 115 | uses: sbt/setup-sbt@v1 116 | 117 | - name: Setup Java (temurin@8) 118 | id: setup-java-temurin-8 119 | if: matrix.java == 'temurin@8' 120 | uses: actions/setup-java@v4 121 | with: 122 | distribution: temurin 123 | java-version: 8 124 | cache: sbt 125 | 126 | - name: sbt update 127 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' 128 | run: sbt +update 129 | 130 | - name: Download target directories (2.12, rootJS) 131 | uses: actions/download-artifact@v4 132 | with: 133 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJS 134 | 135 | - name: Inflate target directories (2.12, rootJS) 136 | run: | 137 | tar xf targets.tar 138 | rm targets.tar 139 | 140 | - name: Download target directories (2.12, rootJVM) 141 | uses: actions/download-artifact@v4 142 | with: 143 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM 144 | 145 | - name: Inflate target directories (2.12, rootJVM) 146 | run: | 147 | tar xf targets.tar 148 | rm targets.tar 149 | 150 | - name: Download target directories (2.12, rootNative) 151 | uses: actions/download-artifact@v4 152 | with: 153 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootNative 154 | 155 | - name: Inflate target directories (2.12, rootNative) 156 | run: | 157 | tar xf targets.tar 158 | rm targets.tar 159 | 160 | - name: Download target directories (2.13, rootJS) 161 | uses: actions/download-artifact@v4 162 | with: 163 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJS 164 | 165 | - name: Inflate target directories (2.13, rootJS) 166 | run: | 167 | tar xf targets.tar 168 | rm targets.tar 169 | 170 | - name: Download target directories (2.13, rootJVM) 171 | uses: actions/download-artifact@v4 172 | with: 173 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM 174 | 175 | - name: Inflate target directories (2.13, rootJVM) 176 | run: | 177 | tar xf targets.tar 178 | rm targets.tar 179 | 180 | - name: Download target directories (2.13, rootNative) 181 | uses: actions/download-artifact@v4 182 | with: 183 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootNative 184 | 185 | - name: Inflate target directories (2.13, rootNative) 186 | run: | 187 | tar xf targets.tar 188 | rm targets.tar 189 | 190 | - name: Download target directories (3, rootJS) 191 | uses: actions/download-artifact@v4 192 | with: 193 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS 194 | 195 | - name: Inflate target directories (3, rootJS) 196 | run: | 197 | tar xf targets.tar 198 | rm targets.tar 199 | 200 | - name: Download target directories (3, rootJVM) 201 | uses: actions/download-artifact@v4 202 | with: 203 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM 204 | 205 | - name: Inflate target directories (3, rootJVM) 206 | run: | 207 | tar xf targets.tar 208 | rm targets.tar 209 | 210 | - name: Download target directories (3, rootNative) 211 | uses: actions/download-artifact@v4 212 | with: 213 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootNative 214 | 215 | - name: Inflate target directories (3, rootNative) 216 | run: | 217 | tar xf targets.tar 218 | rm targets.tar 219 | 220 | - name: Import signing key 221 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' 222 | env: 223 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 224 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 225 | run: echo $PGP_SECRET | base64 -d -i - | gpg --import 226 | 227 | - name: Import signing key and strip passphrase 228 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != '' 229 | env: 230 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 231 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 232 | run: | 233 | echo "$PGP_SECRET" | base64 -d -i - > /tmp/signing-key.gpg 234 | echo "$PGP_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg 235 | (echo "$PGP_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1) 236 | 237 | - name: Publish 238 | env: 239 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 240 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 241 | SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }} 242 | run: sbt tlCiRelease 243 | 244 | dependency-submission: 245 | name: Submit Dependencies 246 | if: github.event.repository.fork == false && github.event_name != 'pull_request' 247 | strategy: 248 | matrix: 249 | os: [ubuntu-22.04] 250 | java: [temurin@8] 251 | runs-on: ${{ matrix.os }} 252 | steps: 253 | - name: Checkout current branch (full) 254 | uses: actions/checkout@v4 255 | with: 256 | fetch-depth: 0 257 | 258 | - name: Setup sbt 259 | uses: sbt/setup-sbt@v1 260 | 261 | - name: Setup Java (temurin@8) 262 | id: setup-java-temurin-8 263 | if: matrix.java == 'temurin@8' 264 | uses: actions/setup-java@v4 265 | with: 266 | distribution: temurin 267 | java-version: 8 268 | cache: sbt 269 | 270 | - name: sbt update 271 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' 272 | run: sbt +update 273 | 274 | - name: Submit Dependencies 275 | uses: scalacenter/sbt-dependency-submission@v2 276 | with: 277 | modules-ignore: rootjs_2.12 rootjs_2.13 rootjs_3 rootjvm_2.12 rootjvm_2.13 rootjvm_3 rootnative_2.12 rootnative_2.13 rootnative_3 278 | configs-ignore: test scala-tool scala-doc-tool test-internal 279 | --------------------------------------------------------------------------------