mirror of https://github.com/sbt/sbt.git
commit
255f9c5553
|
|
@ -0,0 +1,162 @@
|
|||
image:
|
||||
- MacOS
|
||||
- Visual Studio 2015
|
||||
- Visual Studio 2019
|
||||
- Ubuntu
|
||||
|
||||
build: off
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Ubuntu
|
||||
|
||||
branches:
|
||||
only:
|
||||
- build-graal
|
||||
artifacts:
|
||||
- path: client/target/bin/sbtn
|
||||
name: sbtn
|
||||
|
||||
install:
|
||||
- curl -sL https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.tgz > ~/sbt-bin.tgz
|
||||
- mkdir ~/sbt
|
||||
- tar -xf ~/sbt-bin.tgz --directory ~/sbt
|
||||
- curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.11.0/install.sh | bash && . ~/.jabba/jabba.sh
|
||||
- jabba install adopt@1.8.0-275
|
||||
- jabba use adopt@1.8.0-275
|
||||
- curl -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java8-linux-amd64-20.1.0.tar.gz > graalvm.tar.gz
|
||||
- tar -xf graalvm.tar.gz
|
||||
- export PATH="~/sbt/sbt/bin:$PATH"
|
||||
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-275/bin"
|
||||
- export JAVA_HOME="~/.jabba/jdk/adopt@1.8.0-275"
|
||||
|
||||
test_script:
|
||||
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-275/bin"
|
||||
- export PATH="$PATH:graalvm-ce-java8-20.1.0/bin"
|
||||
- gu install native-image
|
||||
- sbt "-Dsbt.io.virtual=false" "-Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/bin/native-image" "sbtClientProj/buildNativeThinClient"
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: MacOS
|
||||
|
||||
branches:
|
||||
only:
|
||||
- build-graal
|
||||
artifacts:
|
||||
- path: client/target/bin/sbtn
|
||||
name: mac-native-sbt-client
|
||||
|
||||
install:
|
||||
- curl -sL https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.tgz > ~/sbt-bin.tgz
|
||||
- mkdir ~/sbt
|
||||
- tar -xf ~/sbt-bin.tgz --directory ~/sbt
|
||||
- curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.11.0/install.sh | bash && . ~/.jabba/jabba.sh
|
||||
- jabba install adopt@1.8.0-222
|
||||
- jabba use adopt@1.8.0-222
|
||||
- curl -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java8-darwin-amd64-20.1.0.tar.gz > graalvm.tar.gz
|
||||
- tar -xf graalvm.tar.gz
|
||||
- export PATH="~/sbt/sbt/bin:$PATH"
|
||||
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/bin"
|
||||
- export JAVA_HOME="~/.jabba/jdk/adopt@1.8.0-222"
|
||||
|
||||
test_script:
|
||||
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/Contents/Home/bin"
|
||||
- export PATH="$PATH:graalvm-ce-java8-20.1.0/Contents/Home/bin"
|
||||
- gu install native-image
|
||||
- sbt "-Dsbt.io.virtual=false" "-Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/Contents/Home/bin/native-image" "sbtClientProj/buildNativeThinClient"
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Visual Studio 2015
|
||||
branches:
|
||||
only:
|
||||
- build-graal
|
||||
|
||||
artifacts:
|
||||
- path: client\target\bin\sbtn.exe
|
||||
name: sbtn.exe
|
||||
install:
|
||||
- cinst adoptopenjdk8 -params 'installdir=C:\\jdk8'
|
||||
- SET CI=true
|
||||
#- choco install windows-sdk-7.1 kb2519277
|
||||
- call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd"
|
||||
|
||||
- ps: |
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
if (!(Test-Path -Path "C:\sbt" )) {
|
||||
(new-object System.Net.WebClient).DownloadFile(
|
||||
'https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.zip',
|
||||
'C:\sbt-bin.zip'
|
||||
)
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\sbt-bin.zip", "C:\sbt")
|
||||
}
|
||||
if (!(Test-Path -Path "C:\graalvm-ce-java8-20.2.0-dev" )) {
|
||||
(new-object System.Net.WebClient).DownloadFile(
|
||||
'https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java8-windows-amd64-20.1.0.zip',
|
||||
'C:\graalvm-ce-java8-20.1.0.zip'
|
||||
)
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\graalvm-ce-java8-20.1.0.zip", "C:\")
|
||||
}
|
||||
if (!(Test-Path -Path "C:\zulu-jdk7" )) {
|
||||
(new-object System.Net.WebClient).DownloadFile(
|
||||
'https://cdn.azul.com/zulu/bin/zulu7.38.0.11-ca-jdk7.0.262-win_x64.zip',
|
||||
'C:\zulu-jdk7.zip'
|
||||
)
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\zulu-jdk7.zip", "C:\")
|
||||
}
|
||||
- SET PATH=C:\graalvm-ce-java8-20.1.0\bin;%PATH%
|
||||
- SET PATH=C:\sbt\sbt\bin;%PATH%
|
||||
- SET JAVA_HOME=C:\jdk8
|
||||
- gu install native-image
|
||||
- rm .sbtopts
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.ivy2\cache'
|
||||
- '%LOCALAPPDATA%\Coursier\Cache\v1'
|
||||
- '%USERPROFILE%\.sbt'
|
||||
|
||||
test_script:
|
||||
- sbt "-Dsbt.io.virtual=false" "-Dsbt.native-image=C:\graalvm-ce-java8-20.1.0\bin\native-image.cmd" "sbtClientProj/buildNativeThinClient"
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Visual Studio 2019
|
||||
branches:
|
||||
except:
|
||||
- build-graal
|
||||
install:
|
||||
- cinst adoptopenjdk8 -params 'installdir=C:\\jdk8'
|
||||
- SET JAVA_HOME=C:\jdk8
|
||||
- SET PATH=C:\jdk8\bin;%PATH%
|
||||
- SET CI=true
|
||||
|
||||
- ps: |
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
if (!(Test-Path -Path "C:\sbt" )) {
|
||||
(new-object System.Net.WebClient).DownloadFile(
|
||||
'https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.zip',
|
||||
'C:\sbt-bin.zip'
|
||||
)
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\sbt-bin.zip", "C:\sbt")
|
||||
}
|
||||
- SET PATH=C:\sbt\sbt\bin;%PATH%
|
||||
- SET SBT_OPTS=-Xmx4g -Dsbt.supershell=never -Dfile.encoding=UTF8
|
||||
- rm .sbtopts
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.ivy2\cache'
|
||||
- '%LOCALAPPDATA%\Coursier\Cache\v1'
|
||||
- '%USERPROFILE%\.sbt'
|
||||
|
||||
test_script:
|
||||
# The server tests often fail in CI when run together so just run a single test to ensure
|
||||
# that the thin client works on windows
|
||||
- sbt "-Dsbt.io.virtual=false" "scripted actions/* reporter/source-mapper classloader-cache/* nio/* watch/*" "serverTestProj/testOnly testpkg.ClientTest"
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
# Set default behaviour, in case users don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files we want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.scala text
|
||||
*.java text
|
||||
# Exclude contraband generated files from diff (by default - you can see it if you want)
|
||||
**/contraband-scala/**/* -diff merge=ours
|
||||
**/contraband-scala/**/* linguist-generated=true
|
||||
**/contraband-scala/**/* diff
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
name: "\U0001F41B Bug report"
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## steps
|
||||
|
||||
<!-- Describe exact steps to reproduce your problems on our computer, including sbt version and build.sbt -->
|
||||
|
||||
## problem
|
||||
|
||||
<!-- Next, describe the problem, or what you think is the problem. -->
|
||||
|
||||
## expectation
|
||||
|
||||
<!-- Describe what you think should've happened. -->
|
||||
|
||||
## notes
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: "\U0001F389 Feature request"
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please use https://discuss.lightbend.com/c/tooling including a specific user story instead of posting them to the issue tracker.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: "❓ Question"
|
||||
about: Please use https://stackoverflow.com/questions/tagged/sbt for questions
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please use https://stackoverflow.com/questions/tagged/sbt for questions instead of posting them to the issue tracker.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
java: 11
|
||||
jobtype: 1
|
||||
- os: ubuntu-latest
|
||||
java: 11
|
||||
jobtype: 2
|
||||
- os: ubuntu-latest
|
||||
java: 11
|
||||
jobtype: 3
|
||||
- os: ubuntu-latest
|
||||
java: 11
|
||||
jobtype: 4
|
||||
- os: ubuntu-latest
|
||||
java: 11
|
||||
jobtype: 5
|
||||
- os: ubuntu-latest
|
||||
java: 8
|
||||
jobtype: 6
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
JAVA_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8
|
||||
SCALA_212: 2.12.13
|
||||
SCALA_213: 2.13.3
|
||||
UTIL_TESTS: utilCache/test;utilControl/test;utilInterface/test;utilLogging/test;utilPosition/test;utilRelation/test;utilScripted/test;utilTracking/test
|
||||
SBT_LOCAL: false
|
||||
steps:
|
||||
- name: Checkout sbt/sbt
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout sbt/io
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: sbt/io
|
||||
ref: develop
|
||||
path: io
|
||||
- name: Checkout sbt/librarymanagement
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: sbt/librarymanagement
|
||||
ref: develop
|
||||
path: librarymanagement
|
||||
- name: Checkout sbt/zinc
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: sbt/zinc
|
||||
ref: develop
|
||||
path: zinc
|
||||
- name: Setup
|
||||
uses: olafurpg/setup-scala@v10
|
||||
with:
|
||||
java-version: "adopt@1.${{ matrix.java }}"
|
||||
- name: Coursier cache
|
||||
uses: coursier/cache-action@v6
|
||||
- name: Cache sbt
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/.sbt
|
||||
key: ${{ runner.os }}-sbt-cache-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
|
||||
- name: Build and test
|
||||
run: |
|
||||
rm -rf "$HOME/.sbt/scripted/" || true
|
||||
case ${{ matrix.jobtype }} in
|
||||
1)
|
||||
sbt -v "mimaReportBinaryIssues ; javafmtCheck ; Test / javafmtCheck; scalafmtCheckAll ; scalafmtSbtCheck; serverTestProj/scalafmtCheckAll; headerCheck ;test:headerCheck ;whitesourceOnPush ;test:compile; publishLocal; test; serverTestProj/test; doc; $UTIL_TESTS; ++$SCALA_213; $UTIL_TESTS"
|
||||
;;
|
||||
2)
|
||||
sbt -v "scripted actions/* apiinfo/* compiler-project/* ivy-deps-management/* reporter/* tests/* watch/* classloader-cache/* package/*"
|
||||
;;
|
||||
3)
|
||||
sbt -v "dependencyTreeProj/publishLocal; scripted dependency-graph/* dependency-management/* plugins/* project-load/* java/* run/* nio/*"
|
||||
;;
|
||||
4)
|
||||
sbt -v "repoOverrideTest:scripted dependency-management/*; scripted source-dependencies/* project/*"
|
||||
;;
|
||||
5)
|
||||
sbt -v "++$SCALA_213!; test;"
|
||||
;;
|
||||
6)
|
||||
# build from fresh IO, LM, and Zinc
|
||||
BUILD_VERSION="1.5.0-SNAPSHOT"
|
||||
cd io
|
||||
sbt -v -Dsbt.build.version=${BUILD_VERSION} +publishLocal
|
||||
cd ../
|
||||
sbt -Dsbtlm.path=$HOME/work/sbt/sbt/librarymanagement -Dsbtzinc.path=$HOME/work/sbt/sbt/zinc -Dsbt.build.version=$BUILD_VERSION -Dsbt.build.fatal=false "+lowerUtils/publishLocal; {librarymanagement}/publishLocal; {zinc}/publishLocal; upperModules/publishLocal"
|
||||
rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true
|
||||
sbt -v -Dsbt.version=$BUILD_VERSION "++$SCALA_213; $UTIL_TESTS; ++$SCALA_212; $UTIL_TESTS; scripted actions/* source-dependencies/*1of3 dependency-management/*1of4 java/*"
|
||||
;;
|
||||
*)
|
||||
echo unknown jobtype
|
||||
exit 1
|
||||
esac
|
||||
rm -rf "$HOME/.sbt/scripted/" || true
|
||||
rm -rf "$HOME/.ivy2/local" || true
|
||||
rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true
|
||||
find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true
|
||||
find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true
|
||||
find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true
|
||||
find $HOME/.sbt -name "*.lock" -delete || true
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
name: Nightly
|
||||
on:
|
||||
schedule:
|
||||
# 08:00 UTC = 03:00 EST
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
java: 8
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
JAVA_OPTS: -Xms800M -Xmx800M -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8
|
||||
steps:
|
||||
- name: Checkout sbt/sbt
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout sbt/io
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: sbt/io
|
||||
ref: develop
|
||||
path: io
|
||||
- name: Checkout sbt/librarymanagement
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: sbt/librarymanagement
|
||||
ref: develop
|
||||
path: librarymanagement
|
||||
- name: Checkout sbt/zinc
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: sbt/zinc
|
||||
ref: develop
|
||||
path: zinc
|
||||
- name: Setup
|
||||
uses: olafurpg/setup-scala@v10
|
||||
with:
|
||||
java-version: "adopt@1.${{ matrix.java }}"
|
||||
- name: Coursier cache
|
||||
uses: coursier/cache-action@v6
|
||||
- name: Build and deploy
|
||||
run: |
|
||||
# build from fresh IO, LM, and Zinc
|
||||
TIMESTAMP=$(TZ=UTC date +%Y%m%dT%H%M%S)
|
||||
export BUILD_VERSION="1.5.0-bin-${TIMESTAMP}"
|
||||
cd io
|
||||
sbt -v +publish
|
||||
cd ../
|
||||
sbt -Dsbtlm.path=$HOME/work/sbt/sbt/librarymanagement -Dsbtzinc.path=$HOME/work/sbt/sbt/zinc -Dsbt.build.fatal=false "+lowerUtils/publish; {librarymanagement}/publish; {zinc}/publish; upperModules/publish; bundledLauncherProj/publish"
|
||||
rm -rf "$HOME/.ivy2/local" || true
|
||||
rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true
|
||||
find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true
|
||||
find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true
|
||||
find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true
|
||||
find $HOME/.sbt -name "*.lock" -delete || true
|
||||
env:
|
||||
BINTRAY_USER: ${{ secrets.BINTRAY_USER }}
|
||||
BINTRAY_PASS: ${{ secrets.BINTRAY_PASS }}
|
||||
BINTRAY_REPOSITORY: maven-snapshots
|
||||
BINTRAY_PACKAGE: sbt
|
||||
|
|
@ -1,2 +1,14 @@
|
|||
target/
|
||||
__pycache__
|
||||
out
|
||||
node_modules
|
||||
vscode-sbt-scala/client/server
|
||||
npm-debug.log
|
||||
*.vsix
|
||||
!sbt/src/server-test/completions/target
|
||||
.big
|
||||
.idea
|
||||
.bloop
|
||||
.metals
|
||||
.bsp/
|
||||
metals.sbt
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
Andrea Peruffo <andrea.peruffo1982@gmail.com>
|
||||
Ethan Atkins <github@ethanatkins.com>
|
||||
Ethan Atkins <github@ethanatkins.com> <ethan.atkins@gmail.com>
|
||||
Eugene Yokota (eed3si9n) <eed3si9n@gmail.com>
|
||||
Jorge Vicente Cantero <jorgevc@fastmail.es>
|
||||
Kenji Yoshida (xuwei-k) <6b656e6a69@gmail.com>
|
||||
Yasuhiro Tatsuno <mogami@exoego.net>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
version = 2.3.2
|
||||
edition = 2019-10
|
||||
maxColumn = 100
|
||||
project.git = true
|
||||
project.excludeFilters = [ "\\Wsbt-test\\W", "\\Winput_sources\\W", "\\Wcontraband-scala\\W" ]
|
||||
lineEndings = preserve
|
||||
|
||||
# https://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style.
|
||||
# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala
|
||||
docstrings = JavaDoc
|
||||
|
||||
# This also seems more idiomatic to include whitespace in import x.{ yyy }
|
||||
spaces.inImportCurlyBraces = true
|
||||
|
||||
# This is more idiomatic Scala.
|
||||
# https://docs.scala-lang.org/style/indentation.html#methods-with-numerous-arguments
|
||||
align.openParenCallSite = false
|
||||
align.openParenDefnSite = false
|
||||
|
||||
# For better code clarity
|
||||
danglingParentheses = true
|
||||
|
||||
trailingCommas = preserve
|
||||
51
.travis.yml
51
.travis.yml
|
|
@ -1,51 +0,0 @@
|
|||
# Use Docker-based container (instead of OpenVZ)
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.ivy2/cache
|
||||
- $HOME/.sbt/boot
|
||||
|
||||
language: scala
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- SBT_CMD=";test:compile;scalariformCheck"
|
||||
- SBT_CMD="mimaReportBinaryIssues"
|
||||
- SBT_CMD="safeUnitTests"
|
||||
- SBT_CMD="otherUnitTests"
|
||||
- SBT_CMD="scripted actions/*"
|
||||
- SBT_CMD="scripted apiinfo/*"
|
||||
- SBT_CMD="scripted compiler-project/*"
|
||||
- SBT_CMD="scripted dependency-management/*1of2"
|
||||
- SBT_CMD="scripted dependency-management/*2of2"
|
||||
- SBT_CMD="scripted ivy-deps-management/*"
|
||||
- SBT_CMD="scripted java/*"
|
||||
- SBT_CMD="scripted package/*"
|
||||
- SBT_CMD="scripted project/*1of2"
|
||||
- SBT_CMD="scripted project/*2of2"
|
||||
- SBT_CMD="scripted reporter/*"
|
||||
- SBT_CMD="scripted run/*"
|
||||
- SBT_CMD="scripted source-dependencies/*1of3"
|
||||
- SBT_CMD="scripted source-dependencies/*2of3"
|
||||
- SBT_CMD="scripted source-dependencies/*3of3"
|
||||
- SBT_CMD="scripted tests/*"
|
||||
- SBT_CMD="scripted project-load/*"
|
||||
- SBT_CMD="repoOverrideTest:scripted dependency-management/*"
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- sbt-dev-bot@googlegroups.com
|
||||
|
||||
script:
|
||||
- sbt -J-XX:ReservedCodeCacheSize=128m "$SBT_CMD"
|
||||
|
||||
before_cache:
|
||||
- find $HOME/.ivy2 -name "ivydata-*.properties" -print -delete
|
||||
- find $HOME/.sbt -name "*.lock" -print -delete
|
||||
182
CONTRIBUTING.md
182
CONTRIBUTING.md
|
|
@ -1,57 +1,72 @@
|
|||
[StackOverflow]: http://stackoverflow.com/tags/sbt
|
||||
[ask]: https://stackoverflow.com/questions/ask?tags=sbt
|
||||
[Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup
|
||||
[StackOverflow]: https://stackoverflow.com/tags/sbt
|
||||
[Setup]: https://www.scala-sbt.org/release/docs/Getting-Started/Setup
|
||||
[Issues]: https://github.com/sbt/sbt/issues
|
||||
[sbt-dev]: https://groups.google.com/d/forum/sbt-dev
|
||||
[subscriptions]: http://typesafe.com/how/subscription
|
||||
[sbt-contrib]: https://gitter.im/sbt/sbt-contrib
|
||||
[327]: https://github.com/sbt/sbt/issues/327
|
||||
[documentation]: https://github.com/sbt/website
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
(For support, see [SUPPORT](./SUPPORT.md))
|
||||
|
||||
There are lots of ways to contribute to sbt ecosystem depending on your interests and skill level.
|
||||
|
||||
- Help someone at work or online fix their build problem.
|
||||
- Answer StackOverflow questions.
|
||||
- Ask StackOverflow questions.
|
||||
- Create plugins that extend sbt's features.
|
||||
- Maintain and update [documentation].
|
||||
- Garden the issue tracker.
|
||||
- Report issues.
|
||||
- Patch the core (send pull requests to code).
|
||||
- On-ramp other contributors.
|
||||
|
||||
Issues and Pull Requests
|
||||
========================
|
||||
------------------------
|
||||
|
||||
When you find a bug in sbt we want to hear about it. Your bug reports play an important part in making sbt more reliable and usable.
|
||||
|
||||
Effective bug reports are more likely to be fixed. These guidelines explain how to write such reports and pull requests.
|
||||
|
||||
Preliminaries
|
||||
--------------
|
||||
Please open a GitHub issue when you are 90% sure it's an actual bug.
|
||||
|
||||
If you have an enhancement idea, or a general discussion, bring it up to [sbt-contrib].
|
||||
|
||||
### Notes about Documentation
|
||||
|
||||
Documentation fixes and contributions are as much welcome as to patching the core. Visit [sbt/website][documentation] to learn about how to contribute.
|
||||
|
||||
### Preliminaries
|
||||
|
||||
- Make sure your sbt version is up to date.
|
||||
- Search [StackOverflow] and [Issues] to see whether your bug has already been reported.
|
||||
- Open one case for each problem.
|
||||
- Proceed to the next steps for details.
|
||||
|
||||
Where to get help and/or file a bug report
|
||||
------------------------------------------
|
||||
|
||||
sbt project uses GitHub Issues as a publicly visible todo list. Please open a GitHub issue only when asked to do so.
|
||||
|
||||
- If you need help with sbt, please [ask] on StackOverflow with the tag "sbt" and the name of the sbt plugin if any.
|
||||
- If you run into an issue, have an enhancement idea, or a general discussion, bring it up to [sbt-dev] Google Group first.
|
||||
- If you need a faster response time, consider one of the [Typesafe subscriptions][subscriptions].
|
||||
|
||||
What to report
|
||||
--------------
|
||||
### What to report
|
||||
|
||||
The developers need three things from you: **steps**, **problems**, and **expectations**.
|
||||
|
||||
### Steps
|
||||
The most important thing to remember about bug reporting is to clearly distinguish facts and opinions.
|
||||
|
||||
The most important thing to remember about bug reporting is to clearly distinguish facts and opinions. What we need first is **the exact steps to reproduce your problems on our computers**. This is called *reproduction steps*, which is often shortened to "repro steps" or "steps." Describe your method of running sbt. Provide `build.sbt` that caused the problem and the version of sbt or Scala that was used. Provide sample Scala code if it's to do with incremental compilation. If possible, minimize the problem to reduce non-essential factors.
|
||||
#### Steps
|
||||
|
||||
What we need first is **the exact steps to reproduce your problems on our computers**. This is called *reproduction steps*, which is often shortened to "repro steps" or "steps." Describe your method of running sbt. Provide `build.sbt` that caused the problem and the version of sbt or Scala that was used. Provide sample Scala code if it's to do with incremental compilation. If possible, minimize the problem to reduce non-essential factors.
|
||||
|
||||
Repro steps are the most important part of a bug report. If we cannot reproduce the problem in one way or the other, the problem can't be fixed. Telling us the error messages is not enough.
|
||||
|
||||
### Problems
|
||||
#### Problems
|
||||
|
||||
Next, describe the problems, or what *you think* is the problem. It might be "obvious" to you that it's a problem, but it could actually be an intentional behavior for some backward compatibility etc. For compilation errors, include the stack trace. The more raw info the better.
|
||||
|
||||
### Expectations
|
||||
#### Expectations
|
||||
|
||||
Same as the problems. Describe what *you think* should've happened.
|
||||
|
||||
### Notes
|
||||
#### Notes
|
||||
|
||||
Add an optional notes section to describe your analysis.
|
||||
Add any optional notes section to describe your analysis.
|
||||
|
||||
### Subject
|
||||
|
||||
|
|
@ -61,7 +76,7 @@ The subject of the bug report doesn't matter. A more descriptive subject is cert
|
|||
|
||||
If possible, please format code or console outputs.
|
||||
|
||||
On Github it's:
|
||||
On GitHub it's:
|
||||
|
||||
```scala
|
||||
name := "foo"
|
||||
|
|
@ -81,14 +96,11 @@ Finally, thank you for taking the time to report a problem.
|
|||
Pull Requests
|
||||
-------------
|
||||
|
||||
### Branch to work against
|
||||
|
||||
Whether implementing a new feature, fixing a bug, or modifying documentation, please work against the latest development branch (currently, 0.13).
|
||||
See below for instructions on building sbt from source.
|
||||
See below for the branch to work against.
|
||||
|
||||
### Adding notes
|
||||
|
||||
All pull requests are required to include a "Notes" file which documents the change. This file should reside in the
|
||||
Most pull requests should include a "Notes" file which documents the change. This file should reside in the
|
||||
directory:
|
||||
|
||||
<sbt root>
|
||||
|
|
@ -111,92 +123,38 @@ Make sure you document each commit and squash them appropriately. You can use th
|
|||
* Scala's documentation on [Git Hygiene](https://github.com/scala/scala/tree/v2.12.0-M3#git-hygiene)
|
||||
* Play's documentation on [Working with Git](https://www.playframework.com/documentation/2.4.4/WorkingWithGit#Squashing-commits)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation fixes and contributions are as much welcome as to the source code itself. Visit [the website project](https://github.com/sbt/website) to learn about how to contribute.
|
||||
|
||||
Build from source
|
||||
=================
|
||||
|
||||
1. Install the current stable binary release of sbt (see [Setup]), which will be used to build sbt from source.
|
||||
2. Get the source code.
|
||||
|
||||
$ git clone git://github.com/sbt/sbt.git
|
||||
$ cd sbt
|
||||
|
||||
3. The default branch is the development branch [0.13](https://github.com/sbt/sbt/tree/0.13), which contains the latest code for the next major sbt release. To build a specific release or commit, switch to the associated tag. The tag for the latest stable release is [v0.13.9](https://github.com/sbt/sbt/tree/v0.13.9):
|
||||
|
||||
$ git checkout v0.13.9
|
||||
|
||||
Note that sbt is always built with the previous stable release. For example, the [0.13](https://github.com/sbt/sbt/tree/0.13) branch is built with 0.13.9 and the [v0.13.9](https://github.com/sbt/sbt/tree/v0.13.9) tag is built with 0.13.8.
|
||||
|
||||
4. To build the launcher and publish all components locally,
|
||||
|
||||
$ sbt
|
||||
> publishLocal
|
||||
|
||||
5. To use this locally built version of sbt, copy your stable `~/bin/sbt` script to `~/bin/xsbt` and change it to use the launcher jar at `<sbt>/launch/target/sbt-launch.jar`.
|
||||
|
||||
Directory `target` is removed by `clean` command. Second solution is using the artifact stored in the local ivy repository.
|
||||
|
||||
The launcher is located in:
|
||||
|
||||
$HOME/.ivy2/local/org.scala-sbt/sbt-launch/0.13.9/jars/sbt-launch.jar
|
||||
|
||||
for v0.13.9 tag, or in:
|
||||
|
||||
$HOME/.ivy2/local/org.scala-sbt/sbt-launch/0.13.10-SNAPSHOT/jars/sbt-launch.jar
|
||||
|
||||
for the development branch.
|
||||
|
||||
## Modifying sbt
|
||||
|
||||
1. When developing sbt itself, run `compile` when checking compilation only.
|
||||
|
||||
2. To use your modified version of sbt in a project locally, run `publishLocal`.
|
||||
|
||||
3. After each `publishLocal`, clean the `~/.sbt/boot/` directory. Alternatively, if sbt is running and the launcher hasn't changed, run `reboot full` to have sbt do this for you.
|
||||
|
||||
4. If a project has `project/build.properties` defined, either delete the file or change `sbt.version` to `0.13.10-SNAPSHOT`.
|
||||
|
||||
## Diagnosing build failures
|
||||
|
||||
Globally included plugins can interfere building `sbt`; if you are getting errors building sbt, try disabling all globally included plugins and try again.
|
||||
|
||||
Running Tests
|
||||
=============
|
||||
|
||||
sbt has an extensive test suite of Unit tests and Integration tests!
|
||||
|
||||
Unit / Functional tests
|
||||
-----------------------
|
||||
|
||||
Various functional and unit tests are defined throughout the
|
||||
project. To run all of them, run `sbt test`. You can run a single test
|
||||
suite with `sbt testOnly`
|
||||
|
||||
Integration tests
|
||||
-----------------
|
||||
|
||||
Scripted integration tests reside in `sbt/src/sbt-test` and are
|
||||
written using the same testing infrastructure sbt plugin authors can
|
||||
use to test their own plugins with sbt. You can read more about this
|
||||
style of tests [here](http://eed3si9n.com/testing-sbt-plugins).
|
||||
See [DEVELOPING](./DEVELOPING.md)
|
||||
|
||||
You can run the integration tests with the `sbt scripted` sbt
|
||||
command. To run a single test, such as the test in
|
||||
`sbt/src/sbt-test/project/global-plugin`, simply run:
|
||||
Profiling sbt
|
||||
-------------
|
||||
|
||||
sbt "scripted project/global-plugin"
|
||||
See [PROFILING](./PROFILING.md)
|
||||
|
||||
Please note that these tests run PAINFULLY slow if the version set in
|
||||
`build.sbt` is set to SNAPSHOT, as every time the scripted test boots
|
||||
up a test instance of sbt, remote mirrors are scanned for possible
|
||||
updates. It is recommended that you set the version suffix to
|
||||
`-devel`, as in `0.13.10-devel`.
|
||||
Other notes for maintainers
|
||||
---------------------------
|
||||
|
||||
Building Documentation
|
||||
======================
|
||||
### Publishing VS Code Extensions
|
||||
|
||||
The scala-sbt.org site documentation is a separate project [website](https://github.com/sbt/website). Follow [the steps in the README](https://github.com/sbt/website#scala-sbtorg) to generate the documentation.
|
||||
Reference https://code.visualstudio.com/docs/extensions/publish-extension
|
||||
|
||||
```
|
||||
$ sbt
|
||||
> vscodePlugin/compile
|
||||
> exit
|
||||
cd vscode-sbt-scala/client
|
||||
# update version number in vscode-sbt-scala/client/package.json
|
||||
$ vsce package
|
||||
$ vsce publish
|
||||
```
|
||||
|
||||
## Signing the CLA
|
||||
|
||||
Contributing to sbt requires you or your employer to sign the
|
||||
[Lightbend Contributor License Agreement](https://www.lightbend.com/contribute/cla).
|
||||
|
||||
To make it easier to respect our license agreements, we have added an sbt task
|
||||
that takes care of adding the LICENSE headers to new files. Run `headerCreate`
|
||||
and sbt will put a copyright notice into it.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
Developer guide
|
||||
===============
|
||||
|
||||
### Branch to work against
|
||||
|
||||
sbt uses two branches for development:
|
||||
|
||||
- Development branch: `develop` (this is also called "master")
|
||||
- Stable branch: `1.$MINOR.x`, where `$MINOR` is current minor version (e.g. `1.1.x` during 1.1.x series)
|
||||
|
||||
|
||||
### Instruction to build just sbt
|
||||
|
||||
If the change you are making is contained in sbt/sbt, you could publishLocal on sbt/sbt:
|
||||
|
||||
```
|
||||
$ sbt
|
||||
sbt:sbtRoot> publishLocal
|
||||
```
|
||||
|
||||
### Instruction to build all modules from source
|
||||
|
||||
1. Install the current stable binary release of sbt (see [Setup]), which will be used to build sbt from source.
|
||||
2. Get the source code.
|
||||
|
||||
```
|
||||
$ mkdir sbt-modules
|
||||
$ cd sbt-modules
|
||||
$ for i in sbt io librarymanagement zinc; do \
|
||||
git clone https://github.com/sbt/$i.git && (cd $i; git checkout -b develop origin/develop)
|
||||
done
|
||||
$ cd sbt
|
||||
$ ./sbt-allsources.sh
|
||||
```
|
||||
|
||||
3. To build and publish all components locally,
|
||||
|
||||
```
|
||||
$ ./sbt-allsources.sh
|
||||
sbt:sbtRoot> publishLocalAllModule
|
||||
```
|
||||
|
||||
### Using the locally built sbt
|
||||
|
||||
The `publishLocal` above will build and publish version `1.$MINOR.$PATCH-SNAPSHOT` (e.g. 1.1.2-SNAPSHOT) to your local ivy repository.
|
||||
|
||||
To use the locally built sbt, set the version in `build.properties` file in your project to `1.$MINOR.$PATCH-SNAPSHOT` then launch `sbt` (this can be the `sbt` launcher installed in your machine).
|
||||
|
||||
```
|
||||
$ cd $YOUR_OWN_PROJECT
|
||||
$ sbt
|
||||
> compile
|
||||
```
|
||||
|
||||
### Nightly builds
|
||||
|
||||
The latest development versions are available as nightly builds on sbt-maven-snapshots (<https://repo.scala-sbt.org/scalasbt/maven-snapshots>) repo, which is a redirect proxy whose underlying repository is subject to change it could be Bintray, Linux box, etc.
|
||||
|
||||
To use a nightly build:
|
||||
|
||||
1. Find out a version from [/org/scala-sbt/sbt/](https://repo.scala-sbt.org/scalasbt/maven-snapshots/org/scala-sbt/sbt/).
|
||||
2. Put the version, for example `sbt.version=1.5.0-bin-20201121T081131` in `project/build.properties`.
|
||||
|
||||
sbt launcher will resolve the specified sbt core artifacts. Because of the aforementioned redirection, this resolution is going to be very slow for the first time you run sbt, and then it should be ok for subsequent runs.
|
||||
|
||||
Unless you're debugging the `sbt` script or the launcher JAR, you should be able to use any recent stable version of sbt installation as the launcher following the [Setup][Setup] instructions first.
|
||||
|
||||
If you're overriding the repositories via `~/.sbt/repositories`, make sure that there's a following entry:
|
||||
|
||||
```
|
||||
[repositories]
|
||||
...
|
||||
sbt-maven-snapshots: https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly
|
||||
```
|
||||
|
||||
### Clearing out boot and local cache
|
||||
|
||||
When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.6/org.scala-sbt/sbt/1.$MINOR.$PATCH-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application.
|
||||
|
||||
One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MINOR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanCache` task.
|
||||
|
||||
### Running sbt "from source" - `sbtOn`
|
||||
|
||||
In addition to locally publishing a build of sbt, there is an alternative, experimental launcher within sbt/sbt
|
||||
to be able to run sbt "from source", that is to compile sbt and run it from its resulting classfiles rather than
|
||||
from published jar files.
|
||||
|
||||
Such a launcher is available within sbt/sbt's build through a custom `sbtOn` command that takes as its first
|
||||
argument the directory on which you want to run sbt, and the remaining arguments are passed _to_ that sbt
|
||||
instance. For example:
|
||||
|
||||
I have setup a minimal sbt build in the directory `/s/t`, to run sbt on that directory I call:
|
||||
|
||||
```bash
|
||||
> sbtOn /s/t
|
||||
[info] Packaging /d/sbt/scripted/sbt/target/scala-2.12/scripted-sbt_2.12-1.2.0-SNAPSHOT.jar ...
|
||||
[info] Done packaging.
|
||||
[info] Running (fork) sbt.RunFromSourceMain /s/t
|
||||
Listening for transport dt_socket at address: 5005
|
||||
[info] Loading settings from idea.sbt,global-plugins.sbt ...
|
||||
[info] Loading global plugins from /Users/dnw/.dotfiles/.sbt/1.0/plugins
|
||||
[info] Loading project definition from /s/t/project
|
||||
[info] Set current project to t (in build file:/s/t/)
|
||||
[info] sbt server started at local:///Users/dnw/.sbt/1.0/server/ce9baa494c7598e4d59b/sock
|
||||
> show baseDirectory
|
||||
[info] /s/t
|
||||
> exit
|
||||
[info] shutting down sbt server
|
||||
[success] Total time: 19 s, completed 25-Apr-2018 15:04:58
|
||||
```
|
||||
|
||||
Please note that this alternative launcher does _not_ have feature parity with sbt/launcher. (Meta)
|
||||
contributions welcome! :-D
|
||||
|
||||
### Diagnosing build failures
|
||||
|
||||
Globally included plugins can interfere building `sbt`; if you are getting errors building sbt, try disabling all globally included plugins and try again.
|
||||
|
||||
### Running Tests
|
||||
|
||||
sbt has a suite of unit tests and integration tests, also known as scripted tests.
|
||||
|
||||
#### Unit / Functional tests
|
||||
|
||||
Various functional and unit tests are defined throughout the
|
||||
project. To run all of them, run `sbt test`. You can run a single test
|
||||
suite with `sbt testOnly`
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Scripted integration tests reside in `sbt/src/sbt-test` and are
|
||||
written using the same testing infrastructure sbt plugin authors can
|
||||
use to test their own plugins with sbt. You can read more about this
|
||||
style of tests [here](https://www.scala-sbt.org/1.0/docs/Testing-sbt-plugins).
|
||||
|
||||
You can run the integration tests with the `sbt scripted` sbt
|
||||
command. To run a single test, such as the test in
|
||||
`sbt/src/sbt-test/project/global-plugin`, simply run:
|
||||
|
||||
sbt "scripted project/global-plugin"
|
||||
|
||||
### Random tidbits
|
||||
|
||||
#### Import statements
|
||||
|
||||
You'd need alternative DSL import since you can't rely on sbt package object.
|
||||
|
||||
```scala
|
||||
// for slash syntax
|
||||
import sbt.SlashSyntax0._
|
||||
|
||||
// for IO
|
||||
import sbt.io.syntax._
|
||||
```
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
(See the guidelines for contributing, linked above)
|
||||
|
||||
## steps
|
||||
|
||||
|
||||
|
||||
## problem
|
||||
|
||||
|
||||
|
||||
## expectation
|
||||
|
||||
|
||||
|
||||
## notes
|
||||
|
||||
222
LICENSE
222
LICENSE
|
|
@ -1,25 +1,203 @@
|
|||
Copyright (c) 2008-2014 Typesafe Inc, Mark Harrah, Grzegorz Kossakowski, Josh Suereth, Indrajit Raychaudhuri, Eugene Yokota, and other contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (c) 2011 - 2018, Lightbend, Inc.
|
||||
Copyright (c) 2008 - 2010, Mark Harrah
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
|
|||
21
MIGRATION.md
21
MIGRATION.md
|
|
@ -1,21 +0,0 @@
|
|||
Migration notes
|
||||
===============
|
||||
|
||||
- Build definition is based on Scala 2.11.8
|
||||
- Build.scala style builds are gone. Use multi-project `build.sbt`.
|
||||
- `Project(...)` constructor is restricted down to two parameters. Use `project` instead.
|
||||
- `sbt.Plugin` is also gone. Use auto plugins.
|
||||
- The incremental compiler, called Zinc, uses class-based name hashing.
|
||||
- Zinc drops support for Scala 2.8.x and 2.9.x.
|
||||
- Removed the pre-0.13.7 *.sbt file parser (previously available under `-Dsbt.parser.simple=true`)
|
||||
- Removed old, hyphen-separated key names (use `publishLocal` instead of `publish-local`)
|
||||
- Removes no-longer-documented old operators `<<=`, `<+=`, and `<++=`.
|
||||
- Renames early command feature from `--<command>` to `early(<command>)`.
|
||||
- Log options `-error`, `-warn`, `-info`, `-debug` are added as shorthand for `"early(error)"` etc.
|
||||
|
||||
#### Additional import required
|
||||
|
||||
Implicit conversions are moved to `sbt.syntax`. Add the following imports to auto plugins
|
||||
or `project/*.scala`.
|
||||
|
||||
import sbt._, syntax._, Keys._
|
||||
6
NOTICE
6
NOTICE
|
|
@ -1,6 +1,7 @@
|
|||
sbt
|
||||
Copyright (c) 2008-2014 Typesafe Inc, Mark Harrah, Grzegorz Kossakowski, Josh Suereth, Indrajit Raychaudhuri, Eugene Yokota, and other contributors.
|
||||
Licensed under BSD-style license (see LICENSE)
|
||||
Copyright 2011 - 2017, Lightbend, Inc.
|
||||
Copyright 2008 - 2010, Mark Harrah
|
||||
Licensed under Apache v2 license (see LICENSE)
|
||||
|
||||
Portions based on code from the Scala compiler. Portions of the Scala
|
||||
library are distributed with the launcher.
|
||||
|
|
@ -33,4 +34,3 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
Profiling sbt
|
||||
-------------
|
||||
|
||||
There are several ways to profile sbt. The new hotness in profiling is FlameGraph.
|
||||
You first collect stack trace samples, and then it is processed into svg graph.
|
||||
See:
|
||||
|
||||
- [Using FlameGraphs To Illuminate The JVM by Nitsan Wakart](https://www.youtube.com/watch?v=ugRrFdda_JQ)
|
||||
- [USENIX ATC '17: Visualizing Performance with Flame Graphs](https://www.youtube.com/watch?v=D53T1Ejig1Q)
|
||||
|
||||
### jvm-profiling-tools/async-profiler
|
||||
|
||||
The first one I recommend is async-profiler. This is available for macOS and Linux,
|
||||
and works fairly well.
|
||||
|
||||
1. Download the installer from https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v1.2
|
||||
2. Make symbolic link to `build/` and `profiler.sh` to `$HOME/bin`, assuming you have PATH to `$HOME/bin`:
|
||||
`ln -s ~/Applications/async-profiler/profiler.sh $HOME/bin/profiler.sh`
|
||||
`ln -s ~/Applications/async-profiler/build $HOME/bin/build`
|
||||
|
||||
Next, close all Java applications and anything that may affect the profiling, and run sbt in one terminal:
|
||||
|
||||
```
|
||||
$ sbt exit
|
||||
```
|
||||
|
||||
In another terminal, run:
|
||||
|
||||
```
|
||||
$ jps
|
||||
92746 sbt-launch.jar
|
||||
92780 Jps
|
||||
```
|
||||
|
||||
This tells you the process ID of sbt. In this case, it's 92746. While it's running, run
|
||||
|
||||
```
|
||||
$ profiler.sh -d 60 <process id>
|
||||
Started [cpu] profiling
|
||||
--- Execution profile ---
|
||||
Total samples: 31602
|
||||
Non-Java: 3239 (10.25%)
|
||||
GC active: 46 (0.15%)
|
||||
Unknown (native): 14667 (46.41%)
|
||||
Not walkable (native): 3 (0.01%)
|
||||
Unknown (Java): 433 (1.37%)
|
||||
Not walkable (Java): 8 (0.03%)
|
||||
Thread exit: 1 (0.00%)
|
||||
Deopt: 9 (0.03%)
|
||||
|
||||
Frame buffer usage: 55.658%
|
||||
|
||||
Total: 1932000000 (6.11%) samples: 1932
|
||||
[ 0] java.lang.ClassLoader$NativeLibrary.load
|
||||
[ 1] java.lang.ClassLoader.loadLibrary0
|
||||
[ 2] java.lang.ClassLoader.loadLibrary
|
||||
[ 3] java.lang.Runtime.loadLibrary0
|
||||
[ 4] java.lang.System.loadLibrary
|
||||
....
|
||||
```
|
||||
|
||||
This should show a bunch of stacktraces that are useful.
|
||||
To visualize this as a flamegraph, run:
|
||||
|
||||
```
|
||||
$ profiler.sh -d 60 -f /tmp/flamegraph.svg <process id>
|
||||
```
|
||||
|
||||
This should produce `/tmp/flamegraph.svg` at the end.
|
||||
|
||||

|
||||
|
||||
See https://gist.github.com/eed3si9n/82d43acc95a002876d357bd8ad5f40d5
|
||||
|
||||
### running sbt with standby
|
||||
|
||||
One of the tricky things you come across while profiling is figuring out the process ID,
|
||||
while wnating to profile the beginning of the application.
|
||||
|
||||
For this purpose, we've added `sbt.launcher.standby` JVM flag.
|
||||
In the next version of sbt, you should be able to run:
|
||||
|
||||
```
|
||||
$ sbt -J-Dsbt.launcher.standby=20s exit
|
||||
```
|
||||
|
||||
This will count down for 20s before doing anything else.
|
||||
|
||||
### jvm-profiling-tools/perf-map-agent
|
||||
|
||||
If you want to try the mixed flamegraph, you can try perf-map-agent.
|
||||
This uses `dtrace` on macOS and `perf` on Linux.
|
||||
|
||||
You first have to compile https://github.com/jvm-profiling-tools/perf-map-agent.
|
||||
For macOS, here to how to export `JAVA_HOME` before running `cmake .`:
|
||||
|
||||
```
|
||||
$ export JAVA_HOME=$(/usr/libexec/java_home)
|
||||
$ cmake .
|
||||
-- The C compiler identification is AppleClang 9.0.0.9000039
|
||||
-- The CXX compiler identification is AppleClang 9.0.0.9000039
|
||||
...
|
||||
$ make
|
||||
```
|
||||
|
||||
In addition, you have to git clone https://github.com/brendangregg/FlameGraph
|
||||
|
||||
In a fresh termimal, run sbt with `-XX:+PreserveFramePointer` flag:
|
||||
|
||||
```
|
||||
$ sbt -J-Dsbt.launcher.standby=20s -J-XX:+PreserveFramePointer exit
|
||||
```
|
||||
|
||||
In the terminal that you will run the perf-map:
|
||||
|
||||
```
|
||||
$ cd quicktest/
|
||||
$ export JAVA_HOME=$(/usr/libexec/java_home)
|
||||
$ export FLAMEGRAPH_DIR=$HOME/work/FlameGraph
|
||||
$ jps
|
||||
94592 Jps
|
||||
94549 sbt-launch.jar
|
||||
$ $HOME/work/perf-map-agent/bin/dtrace-java-flames 94549
|
||||
dtrace: system integrity protection is on, some features will not be available
|
||||
|
||||
dtrace: description 'profile-99 ' matched 2 probes
|
||||
Flame graph SVG written to DTRACE_FLAME_OUTPUT='/Users/xxx/work/quicktest/flamegraph-94549.svg'.
|
||||
```
|
||||
|
||||
This would produce better flamegraph in theory, but the output looks too messy for `sbt exit` case.
|
||||
See https://gist.github.com/eed3si9n/b5856ff3d987655513380d1a551aa0df
|
||||
This might be because it assumes that the operations are already JITed.
|
||||
|
||||
### ktoso/sbt-jmh
|
||||
|
||||
https://github.com/ktoso/sbt-jmh
|
||||
|
||||
Due to JIT warmup etc, benchmarking is difficult. JMH runs the same tests multiple times to
|
||||
remove these effects and comes closer to measuring the performance of your code.
|
||||
|
||||
There's also an integration with jvm-profiling-tools/async-profiler, apparently.
|
||||
|
||||
### VisualVM
|
||||
|
||||
I'd also mention traditional JVM profiling tool. Since VisualVM is opensource,
|
||||
I'll mention this one: https://visualvm.github.io/
|
||||
|
||||
1. First VisualVM.
|
||||
2. Start sbt from a terminal.
|
||||
3. You should see `xsbt.boot.Boot` under Local.
|
||||
4. Open it, and select either sampler or profiler, and hit CPU button at the point when you want to start.
|
||||
|
||||
If you are familiar with YourKit, it also works similarly.
|
||||
|
|
@ -1 +0,0 @@
|
|||
(See the guidelines for contributing, linked above)
|
||||
22
README.md
22
README.md
|
|
@ -1,13 +1,16 @@
|
|||
[](https://travis-ci.com/github/sbt/sbt)
|
||||
[](https://index.scala-lang.org/sbt/sbt)
|
||||
[](https://gitter.im/sbt/sbt)
|
||||
|
||||
[sbt/sbt-zero-seven]: https://github.com/sbt/sbt-zero-seven
|
||||
[CONTRIBUTING]: CONTRIBUTING.md
|
||||
[Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup
|
||||
[FAQ]: http://www.scala-sbt.org/release/docs/Faq.html
|
||||
[Setup]: https://www.scala-sbt.org/release/docs/Getting-Started/Setup
|
||||
[FAQ]: https://www.scala-sbt.org/release/docs/Faq.html
|
||||
[sbt-dev]: https://groups.google.com/d/forum/sbt-dev
|
||||
[searching]: http://stackoverflow.com/tags/sbt
|
||||
[searching]: https://stackoverflow.com/tags/sbt
|
||||
[asking]: https://stackoverflow.com/questions/ask?tags=sbt
|
||||
[LICENSE]: LICENSE
|
||||
[sbt/io]: https://github.com/sbt/io
|
||||
[sbt/util]: https://github.com/sbt/util
|
||||
[sbt/librarymanagement]: https://github.com/sbt/librarymanagement
|
||||
[sbt/zinc]: https://github.com/sbt/zinc
|
||||
[sbt/sbt]: https://github.com/sbt/sbt
|
||||
|
|
@ -17,16 +20,15 @@ sbt
|
|||
|
||||
sbt is a build tool for Scala, Java, and more.
|
||||
|
||||
For general documentation, see http://www.scala-sbt.org/.
|
||||
For general documentation, see https://www.scala-sbt.org/.
|
||||
|
||||
sbt 1.0.x
|
||||
sbt 1.x
|
||||
---------
|
||||
|
||||
This is the 1.0.x series of sbt. The source code of sbt is split across
|
||||
several Github repositories, including this one.
|
||||
This is the 1.x series of sbt. The source code of sbt is split across
|
||||
several GitHub repositories, including this one.
|
||||
|
||||
- [sbt/io][sbt/io] hosts `sbt.io` module.
|
||||
- [sbt/util][sbt/util] hosts a collection of internally used modules.
|
||||
- [sbt/librarymanagement][sbt/librarymanagement] hosts `sbt.librarymanagement` module that wraps Ivy.
|
||||
- [sbt/zinc][sbt/zinc] hosts Zinc, an incremental compiler for Scala.
|
||||
- [sbt/sbt][sbt/sbt], this repository hosts modules that implements the build tool.
|
||||
|
|
@ -42,7 +44,7 @@ Issues and Pull Requests
|
|||
|
||||
Please read [CONTRIBUTING] carefully before opening a GitHub Issue.
|
||||
|
||||
The short version: try [searching] or [asking] on StackOverflow and [sbt-dev].
|
||||
The short version: try [searching] or [asking] on StackOverflow.
|
||||
|
||||
license
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
[ask]: https://stackoverflow.com/questions/ask?tags=sbt
|
||||
[Lightbend]: https://www.lightbend.com/
|
||||
[subscriptions]: https://www.lightbend.com/platform/subscription
|
||||
[gitter]: https://gitter.im/sbt/sbt
|
||||
|
||||
Support
|
||||
=======
|
||||
|
||||
[Lightbend] sponsors sbt and encourages contributions from the active community. Enterprises can adopt it for mission critical systems with confidence because Lightbend stands behind sbt with commercial support and services.
|
||||
|
||||
For community support please [ask] on StackOverflow with the tag "sbt" (and the name of the sbt plugin(s) if any).
|
||||
|
||||
- State the problem or question clearly and provide enough context. Code examples and `build.sbt` are often useful when appropriately edited.
|
||||
- There's also [Gitter sbt/sbt room][gitter], but Stackoverflow is recommended so others can benefit from the answers.
|
||||
|
||||
For professional support, for instance if you need faster response times, [Lightbend], the maintainer of Scala compiler and sbt, provides:
|
||||
|
||||
- [Lightbend Subscriptions][subscriptions], which includes Expert Support
|
||||
- Training
|
||||
- Consulting
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#compdef sbtn
|
||||
|
||||
COMPLETE="--completions=${words[@]}"
|
||||
COMPLETIONS=($(sbtn --no-tab ${COMPLETE}))
|
||||
_alternative 'arguments:custom arg:($COMPLETIONS)'
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
_do_sbtn_completions() {
|
||||
COMPREPLY=($(sbtn "--completions=${COMP_LINE}"))
|
||||
}
|
||||
|
||||
complete -F _do_sbtn_completions sbtn
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
function __sbtcomp
|
||||
sbtn --completions="$argv"
|
||||
end
|
||||
complete --command sbtn -f --arguments '(__sbtcomp (commandline -cp))'
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
$scriptblock = {
|
||||
param($commandName, $line, $position)
|
||||
$len = $line.ToString().length
|
||||
$spaces = " " * ($position - $len)
|
||||
$arg="--completions=$line$spaces"
|
||||
& 'sbtn.exe' @('--no-tab', '--no-stderr', $arg)
|
||||
}
|
||||
Set-Alias -Name sbtn -Value sbtn.exe
|
||||
Register-ArgumentCompleter -CommandName sbtn.exe -ScriptBlock $scriptBlock
|
||||
Register-ArgumentCompleter -CommandName sbtn -ScriptBlock $scriptBlock
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.client;
|
||||
|
||||
import sbt.internal.client.NetworkClient;
|
||||
import java.nio.file.Paths;
|
||||
import org.fusesource.jansi.AnsiConsole;
|
||||
|
||||
public class Client {
|
||||
public static void main(final String[] args) {
|
||||
boolean isWin = System.getProperty("os.name").toLowerCase().startsWith("win");
|
||||
try {
|
||||
if (isWin) AnsiConsole.systemInstall();
|
||||
NetworkClient.main(args);
|
||||
} catch (final Throwable t) {
|
||||
t.printStackTrace();
|
||||
} finally {
|
||||
if (isWin) AnsiConsole.systemUninstall();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"name":"java.lang.ProcessBuilder",
|
||||
"methods":[{"name":"redirectInput","parameterTypes":["java.lang.ProcessBuilder$Redirect"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.lang.ProcessBuilder$Redirect",
|
||||
"fields":[{"name":"INHERIT"}]
|
||||
},
|
||||
{
|
||||
"name":"java.lang.System",
|
||||
"methods":[{"name":"console","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"jline.UnixTerminal",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"resources":[
|
||||
{"pattern":"jline/console/completer/CandidateListCompletionHandler.properties"},
|
||||
{"pattern":"org/jline/utils/ansi.caps"},
|
||||
{"pattern":"org/jline/utils/capabilities.txt"},
|
||||
{"pattern":"org/jline/utils/colors.txt"},
|
||||
{"pattern":"org/jline/utils/dumb-color.caps"},
|
||||
{"pattern":"org/jline/utils/xterm.caps"},
|
||||
{"pattern":"org/jline/utils/xterm-256color.caps"},
|
||||
{"pattern":"org/jline/utils/windows-256color.caps"},
|
||||
{"pattern":"org/jline/utils/screen-256color.caps"},
|
||||
{"pattern":"org/jline/utils/windows.caps"},
|
||||
{"pattern":"org/jline/utils/windows-conemu.caps"},
|
||||
{"pattern":"org/jline/utils/dumb.caps"},
|
||||
{"pattern":"org/jline/utils/windows-vtp.caps"},
|
||||
{"pattern":"org/jline/utils/screen.caps"},
|
||||
{"pattern":"library.properties"},
|
||||
{"pattern":"darwin/x86_64/libsbtipcsocket.dylib"},
|
||||
{"pattern":"linux/x86_64/libsbtipcsocket.so"},
|
||||
{"pattern":"win32/x86_64/sbtipcsocket.dll"}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import scala.reflect._
|
||||
import macros._
|
||||
import ContextUtil.{ DynamicDependencyError, DynamicReferenceError }
|
||||
|
||||
object ContextUtil {
|
||||
final val DynamicDependencyError = "Illegal dynamic dependency"
|
||||
final val DynamicReferenceError = "Illegal dynamic reference"
|
||||
|
||||
/**
|
||||
* Constructs an object with utility methods for operating in the provided macro context `c`.
|
||||
* Callers should explicitly specify the type parameter as `c.type` in order to preserve the path dependent types.
|
||||
*/
|
||||
def apply[C <: blackbox.Context with Singleton](c: C): ContextUtil[C] = new ContextUtil(c: C)
|
||||
|
||||
/**
|
||||
* Helper for implementing a no-argument macro that is introduced via an implicit.
|
||||
* This method removes the implicit conversion and evaluates the function `f` on the target of the conversion.
|
||||
*
|
||||
* Given `myImplicitConversion(someValue).extensionMethod`, where `extensionMethod` is a macro that uses this
|
||||
* method, the result of this method is `f(<Tree of someValue>)`.
|
||||
*/
|
||||
def selectMacroImpl[T: c.WeakTypeTag](
|
||||
c: blackbox.Context
|
||||
)(f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] = {
|
||||
import c.universe._
|
||||
|
||||
c.macroApplication match {
|
||||
case s @ Select(Apply(_, t :: Nil), _) => f(c.Expr[Any](t), s.pos)
|
||||
case a @ Apply(_, t :: Nil) => f(c.Expr[Any](t), a.pos)
|
||||
case x => unexpectedTree(x)
|
||||
}
|
||||
}
|
||||
|
||||
def unexpectedTree[C <: blackbox.Context](tree: C#Tree): Nothing =
|
||||
sys.error("Unexpected macro application tree (" + tree.getClass + "): " + tree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility methods for macros. Several methods assume that the context's universe is a full compiler
|
||||
* (`scala.tools.nsc.Global`).
|
||||
* This is not thread safe due to the underlying Context and related data structures not being thread safe.
|
||||
* Use `ContextUtil[c.type](c)` to construct.
|
||||
*/
|
||||
final class ContextUtil[C <: blackbox.Context](val ctx: C) {
|
||||
import ctx.universe.{ Apply => ApplyTree, _ }
|
||||
import internal.decorators._
|
||||
|
||||
val powerContext = ctx.asInstanceOf[reflect.macros.runtime.Context]
|
||||
val global: powerContext.universe.type = powerContext.universe
|
||||
def callsiteTyper: global.analyzer.Typer = powerContext.callsiteTyper
|
||||
val initialOwner: Symbol = callsiteTyper.context.owner.asInstanceOf[ctx.universe.Symbol]
|
||||
|
||||
lazy val alistType = ctx.typeOf[AList[KList]]
|
||||
lazy val alist: Symbol = alistType.typeSymbol.companion
|
||||
lazy val alistTC: Type = alistType.typeConstructor
|
||||
|
||||
/** Modifiers for a local val.*/
|
||||
lazy val localModifiers = Modifiers(NoFlags)
|
||||
|
||||
def getPos(sym: Symbol) = if (sym eq null) NoPosition else sym.pos
|
||||
|
||||
/**
|
||||
* Constructs a unique term name with the given prefix within this Context.
|
||||
* (The current implementation uses Context.freshName, which increments
|
||||
*/
|
||||
def freshTermName(prefix: String) = TermName(ctx.freshName("$" + prefix))
|
||||
|
||||
/**
|
||||
* Constructs a new, synthetic, local ValDef Type `tpe`, a unique name,
|
||||
* Position `pos`, an empty implementation (no rhs), and owned by `owner`.
|
||||
*/
|
||||
def freshValDef(tpe: Type, pos: Position, owner: Symbol): ValDef = {
|
||||
val SYNTHETIC = (1 << 21).toLong.asInstanceOf[FlagSet]
|
||||
val sym = owner.newTermSymbol(freshTermName("q"), pos, SYNTHETIC)
|
||||
setInfo(sym, tpe)
|
||||
val vd = internal.valDef(sym, EmptyTree)
|
||||
vd.setPos(pos)
|
||||
vd
|
||||
}
|
||||
|
||||
lazy val parameterModifiers = Modifiers(Flag.PARAM)
|
||||
|
||||
/**
|
||||
* Collects all definitions in the tree for use in checkReferences.
|
||||
* This excludes definitions in wrapped expressions because checkReferences won't allow nested dereferencing anyway.
|
||||
*/
|
||||
def collectDefs(
|
||||
tree: Tree,
|
||||
isWrapper: (String, Type, Tree) => Boolean
|
||||
): collection.Set[Symbol] = {
|
||||
val defs = new collection.mutable.HashSet[Symbol]
|
||||
// adds the symbols for all non-Ident subtrees to `defs`.
|
||||
val process = new Traverser {
|
||||
override def traverse(t: Tree) = t match {
|
||||
case _: Ident => ()
|
||||
case ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil)
|
||||
if isWrapper(nme.decodedName.toString, tpe.tpe, qual) =>
|
||||
()
|
||||
case tree =>
|
||||
if (tree.symbol ne null) {
|
||||
defs += tree.symbol
|
||||
()
|
||||
}
|
||||
super.traverse(tree)
|
||||
}
|
||||
}
|
||||
process.traverse(tree)
|
||||
defs
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference is illegal if it is to an M instance defined within the scope of the macro call.
|
||||
* As an approximation, disallow referenced to any local definitions `defs`.
|
||||
*/
|
||||
def illegalReference(defs: collection.Set[Symbol], sym: Symbol, mType: Type): Boolean =
|
||||
sym != null && sym != NoSymbol && defs.contains(sym) && {
|
||||
sym match {
|
||||
case m: MethodSymbol => m.returnType.erasure <:< mType
|
||||
case _ => sym.typeSignature <:< mType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference is illegal if it is to an M instance defined within the scope of the macro call.
|
||||
* As an approximation, disallow referenced to any local definitions `defs`.
|
||||
*/
|
||||
def illegalReference(defs: collection.Set[Symbol], sym: Symbol): Boolean =
|
||||
illegalReference(defs, sym, weakTypeOf[Any])
|
||||
|
||||
type PropertyChecker = (String, Type, Tree) => Boolean
|
||||
|
||||
/**
|
||||
* A function that checks the provided tree for illegal references to M instances defined in the
|
||||
* expression passed to the macro and for illegal dereferencing of M instances.
|
||||
*/
|
||||
def checkReferences(
|
||||
defs: collection.Set[Symbol],
|
||||
isWrapper: PropertyChecker,
|
||||
mType: Type
|
||||
): Tree => Unit = {
|
||||
case s @ ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) =>
|
||||
if (isWrapper(nme.decodedName.toString, tpe.tpe, qual)) {
|
||||
ctx.error(s.pos, DynamicDependencyError)
|
||||
}
|
||||
case id @ Ident(name) if illegalReference(defs, id.symbol, mType) =>
|
||||
ctx.error(id.pos, DynamicReferenceError + ": " + name)
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
@deprecated("Use that variant that specifies the M instance types to exclude", since = "1.3.0")
|
||||
/**
|
||||
* A function that checks the provided tree for illegal references to M instances defined in the
|
||||
* expression passed to the macro and for illegal dereferencing of M instances.
|
||||
*/
|
||||
def checkReferences(defs: collection.Set[Symbol], isWrapper: PropertyChecker): Tree => Unit =
|
||||
checkReferences(defs, isWrapper, weakTypeOf[Any])
|
||||
|
||||
/** Constructs a ValDef with a parameter modifier, a unique name, with the provided Type and with an empty rhs. */
|
||||
def freshMethodParameter(tpe: Type): ValDef =
|
||||
ValDef(parameterModifiers, freshTermName("p"), TypeTree(tpe), EmptyTree)
|
||||
|
||||
/** Constructs a ValDef with local modifiers and a unique name. */
|
||||
def localValDef(tpt: Tree, rhs: Tree): ValDef =
|
||||
ValDef(localModifiers, freshTermName("q"), tpt, rhs)
|
||||
|
||||
/** Constructs a tuple value of the right TupleN type from the provided inputs.*/
|
||||
def mkTuple(args: List[Tree]): Tree =
|
||||
global.gen.mkTuple(args.asInstanceOf[List[global.Tree]]).asInstanceOf[ctx.universe.Tree]
|
||||
|
||||
def setSymbol[_Tree](t: _Tree, sym: Symbol): Unit = {
|
||||
t.asInstanceOf[global.Tree].setSymbol(sym.asInstanceOf[global.Symbol])
|
||||
()
|
||||
}
|
||||
def setInfo(sym: Symbol, tpe: Type): Unit = {
|
||||
sym.asInstanceOf[global.Symbol].setInfo(tpe.asInstanceOf[global.Type])
|
||||
()
|
||||
}
|
||||
|
||||
/** Creates a new, synthetic type variable with the specified `owner`. */
|
||||
def newTypeVariable(owner: Symbol, prefix: String = "T0"): TypeSymbol =
|
||||
owner
|
||||
.asInstanceOf[global.Symbol]
|
||||
.newSyntheticTypeParam(prefix, 0L)
|
||||
.asInstanceOf[ctx.universe.TypeSymbol]
|
||||
|
||||
/** The type representing the type constructor `[X] X` */
|
||||
lazy val idTC: Type = {
|
||||
val tvar = newTypeVariable(NoSymbol)
|
||||
internal.polyType(tvar :: Nil, refVar(tvar))
|
||||
}
|
||||
|
||||
/** A Type that references the given type variable. */
|
||||
def refVar(variable: TypeSymbol): Type = variable.toTypeConstructor
|
||||
|
||||
/** Constructs a new, synthetic type variable that is a type constructor. For example, in type Y[L[x]], L is such a type variable. */
|
||||
def newTCVariable(owner: Symbol): TypeSymbol = {
|
||||
val tc = newTypeVariable(owner)
|
||||
val arg = newTypeVariable(tc, "x");
|
||||
tc.setInfo(internal.polyType(arg :: Nil, emptyTypeBounds))
|
||||
tc
|
||||
}
|
||||
|
||||
/** >: Nothing <: Any */
|
||||
def emptyTypeBounds: TypeBounds =
|
||||
internal.typeBounds(definitions.NothingClass.toType, definitions.AnyClass.toType)
|
||||
|
||||
/** Creates a new anonymous function symbol with Position `pos`. */
|
||||
def functionSymbol(pos: Position): Symbol =
|
||||
callsiteTyper.context.owner
|
||||
.newAnonymousFunctionValue(pos.asInstanceOf[global.Position])
|
||||
.asInstanceOf[ctx.universe.Symbol]
|
||||
|
||||
def functionType(args: List[Type], result: Type): Type = {
|
||||
val tpe = global.definitions
|
||||
.functionType(args.asInstanceOf[List[global.Type]], result.asInstanceOf[global.Type])
|
||||
tpe.asInstanceOf[Type]
|
||||
}
|
||||
|
||||
/** Create a Tree that references the `val` represented by `vd`, copying attributes from `replaced`. */
|
||||
def refVal(replaced: Tree, vd: ValDef): Tree =
|
||||
treeCopy.Ident(replaced, vd.name).setSymbol(vd.symbol)
|
||||
|
||||
/** Creates a Function tree using `functionSym` as the Symbol and changing `initialOwner` to `functionSym` in `body`.*/
|
||||
def createFunction(params: List[ValDef], body: Tree, functionSym: Symbol): Tree = {
|
||||
changeOwner(body, initialOwner, functionSym)
|
||||
val f = Function(params, body)
|
||||
setSymbol(f, functionSym)
|
||||
f
|
||||
}
|
||||
|
||||
def changeOwner(tree: Tree, prev: Symbol, next: Symbol): Unit =
|
||||
new ChangeOwnerAndModuleClassTraverser(
|
||||
prev.asInstanceOf[global.Symbol],
|
||||
next.asInstanceOf[global.Symbol]
|
||||
).traverse(tree.asInstanceOf[global.Tree])
|
||||
|
||||
// Workaround copied from scala/async:can be removed once https://github.com/scala/scala/pull/3179 is merged.
|
||||
private[this] class ChangeOwnerAndModuleClassTraverser(
|
||||
oldowner: global.Symbol,
|
||||
newowner: global.Symbol
|
||||
) extends global.ChangeOwnerTraverser(oldowner, newowner) {
|
||||
override def traverse(tree: global.Tree): Unit = {
|
||||
tree match {
|
||||
case _: global.DefTree => change(tree.symbol.moduleClass)
|
||||
case _ =>
|
||||
}
|
||||
super.traverse(tree)
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the Symbol that references the statically accessible singleton `i`. */
|
||||
def singleton[T <: AnyRef with Singleton](i: T)(implicit it: ctx.TypeTag[i.type]): Symbol =
|
||||
it.tpe match {
|
||||
case SingleType(_, sym) if !sym.isFreeTerm && sym.isStatic => sym
|
||||
case x => sys.error("Instance must be static (was " + x + ").")
|
||||
}
|
||||
|
||||
def select(t: Tree, name: String): Tree = Select(t, TermName(name))
|
||||
|
||||
/** Returns the symbol for the non-private method named `name` for the class/module `obj`. */
|
||||
def method(obj: Symbol, name: String): Symbol = {
|
||||
val ts: Type = obj.typeSignature
|
||||
val m: global.Symbol = ts.asInstanceOf[global.Type].nonPrivateMember(global.newTermName(name))
|
||||
m.asInstanceOf[Symbol]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Type representing the type constructor tcp.<name>. For example, given
|
||||
* `object Demo { type M[x] = List[x] }`, the call `extractTC(Demo, "M")` will return a type representing
|
||||
* the type constructor `[x] List[x]`.
|
||||
*/
|
||||
def extractTC(tcp: AnyRef with Singleton, name: String)(
|
||||
implicit it: ctx.TypeTag[tcp.type]
|
||||
): ctx.Type = {
|
||||
val itTpe = it.tpe.asInstanceOf[global.Type]
|
||||
val m = itTpe.nonPrivateMember(global.newTypeName(name))
|
||||
val tc = itTpe.memberInfo(m).asInstanceOf[ctx.universe.Type]
|
||||
assert(tc != NoType && tc.takesTypeArgs, "Invalid type constructor: " + tc)
|
||||
tc
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitutes wrappers in tree `t` with the result of `subWrapper`.
|
||||
* A wrapper is a Tree of the form `f[T](v)` for which isWrapper(<Tree of f>, <Underlying Type>, <qual>.target) returns true.
|
||||
* Typically, `f` is a `Select` or `Ident`.
|
||||
* The wrapper is replaced with the result of `subWrapper(<Type of T>, <Tree of v>, <wrapper Tree>)`
|
||||
*/
|
||||
def transformWrappers(
|
||||
t: Tree,
|
||||
subWrapper: (String, Type, Tree, Tree) => Converted[ctx.type]
|
||||
): Tree = {
|
||||
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
|
||||
// plain Idents that reference the actual input value
|
||||
object appTransformer extends Transformer {
|
||||
override def transform(tree: Tree): Tree =
|
||||
tree match {
|
||||
case ApplyTree(TypeApply(Select(_, nme), targ :: Nil), qual :: Nil) =>
|
||||
subWrapper(nme.decodedName.toString, targ.tpe, qual, tree) match {
|
||||
case Converted.Success(t, finalTx) =>
|
||||
changeOwner(qual, currentOwner, initialOwner) // Fixes https://github.com/sbt/sbt/issues/1150
|
||||
finalTx(t)
|
||||
case Converted.Failure(p, m) => ctx.abort(p, m)
|
||||
case _: Converted.NotApplicable[_] => super.transform(tree)
|
||||
}
|
||||
case _ => super.transform(tree)
|
||||
}
|
||||
}
|
||||
appTransformer.atOwner(initialOwner) {
|
||||
appTransformer.transform(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import scala.reflect._
|
||||
import macros._
|
||||
import Types.idFun
|
||||
|
||||
abstract class Convert {
|
||||
def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type]
|
||||
def asPredicate(c: blackbox.Context): (String, c.Type, c.Tree) => Boolean =
|
||||
(n, tpe, tree) => {
|
||||
val tag = c.WeakTypeTag(tpe)
|
||||
apply(c)(n, tree)(tag).isSuccess
|
||||
}
|
||||
}
|
||||
sealed trait Converted[C <: blackbox.Context with Singleton] {
|
||||
def isSuccess: Boolean
|
||||
def transform(f: C#Tree => C#Tree): Converted[C]
|
||||
}
|
||||
object Converted {
|
||||
def NotApplicable[C <: blackbox.Context with Singleton] = new NotApplicable[C]
|
||||
final case class Failure[C <: blackbox.Context with Singleton](
|
||||
position: C#Position,
|
||||
message: String
|
||||
) extends Converted[C] {
|
||||
def isSuccess = false
|
||||
def transform(f: C#Tree => C#Tree): Converted[C] = new Failure(position, message)
|
||||
}
|
||||
final class NotApplicable[C <: blackbox.Context with Singleton] extends Converted[C] {
|
||||
def isSuccess = false
|
||||
def transform(f: C#Tree => C#Tree): Converted[C] = this
|
||||
}
|
||||
final case class Success[C <: blackbox.Context with Singleton](
|
||||
tree: C#Tree,
|
||||
finalTransform: C#Tree => C#Tree
|
||||
) extends Converted[C] {
|
||||
def isSuccess = true
|
||||
def transform(f: C#Tree => C#Tree): Converted[C] = Success(f(tree), finalTransform)
|
||||
}
|
||||
object Success {
|
||||
def apply[C <: blackbox.Context with Singleton](tree: C#Tree): Success[C] =
|
||||
Success(tree, idFun)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import sbt.internal.util.Classes.Applicative
|
||||
import sbt.internal.util.Types.Id
|
||||
|
||||
/**
|
||||
* The separate hierarchy from Applicative/Monad is for two reasons.
|
||||
*
|
||||
* 1. The type constructor is represented as an abstract type because a TypeTag cannot represent a type constructor directly.
|
||||
* 2. The applicative interface is uncurried.
|
||||
*/
|
||||
trait Instance {
|
||||
type M[x]
|
||||
def app[K[L[x]], Z](in: K[M], f: K[Id] => Z)(implicit a: AList[K]): M[Z]
|
||||
def map[S, T](in: M[S], f: S => T): M[T]
|
||||
def pure[T](t: () => T): M[T]
|
||||
}
|
||||
|
||||
trait MonadInstance extends Instance {
|
||||
def flatten[T](in: M[M[T]]): M[T]
|
||||
}
|
||||
|
||||
import scala.reflect.macros._
|
||||
|
||||
object Instance {
|
||||
type Aux[M0[_]] = Instance { type M[x] = M0[x] }
|
||||
type Aux2[M0[_], N[_]] = Instance { type M[x] = M0[N[x]] }
|
||||
|
||||
final val ApplyName = "app"
|
||||
final val FlattenName = "flatten"
|
||||
final val PureName = "pure"
|
||||
final val MapName = "map"
|
||||
final val InstanceTCName = "M"
|
||||
|
||||
final class Input[U <: Universe with Singleton](
|
||||
val tpe: U#Type,
|
||||
val expr: U#Tree,
|
||||
val local: U#ValDef
|
||||
)
|
||||
trait Transform[C <: blackbox.Context with Singleton, N[_]] {
|
||||
def apply(in: C#Tree): C#Tree
|
||||
}
|
||||
def idTransform[C <: blackbox.Context with Singleton]: Transform[C, Id] = in => in
|
||||
|
||||
/**
|
||||
* Implementation of a macro that provides a direct syntax for applicative functors and monads.
|
||||
* It is intended to be used in conjunction with another macro that conditions the inputs.
|
||||
*
|
||||
* This method processes the Tree `t` to find inputs of the form `wrap[T]( input )`
|
||||
* This form is typically constructed by another macro that pretends to be able to get a value of type `T`
|
||||
* from a value convertible to `M[T]`. This `wrap(input)` form has two main purposes.
|
||||
* First, it identifies the inputs that should be transformed.
|
||||
* Second, it allows the input trees to be wrapped for later conversion into the appropriate `M[T]` type by `convert`.
|
||||
* This wrapping is necessary because applying the first macro must preserve the original type,
|
||||
* but it is useful to delay conversion until the outer, second macro is called. The `wrap` method accomplishes this by
|
||||
* allowing the original `Tree` and `Type` to be hidden behind the raw `T` type. This method will remove the call to `wrap`
|
||||
* so that it is not actually called at runtime.
|
||||
*
|
||||
* Each `input` in each expression of the form `wrap[T]( input )` is transformed by `convert`.
|
||||
* This transformation converts the input Tree to a Tree of type `M[T]`.
|
||||
* The original wrapped expression `wrap(input)` is replaced by a reference to a new local `val x: T`, where `x` is a fresh name.
|
||||
* These converted inputs are passed to `builder` as well as the list of these synthetic `ValDef`s.
|
||||
* The `TupleBuilder` instance constructs a tuple (Tree) from the inputs and defines the right hand side of the vals
|
||||
* that unpacks the tuple containing the results of the inputs.
|
||||
*
|
||||
* The constructed tuple of inputs and the code that unpacks the results of the inputs are then passed to the `i`,
|
||||
* which is an implementation of `Instance` that is statically accessible.
|
||||
* An Instance defines a applicative functor associated with a specific type constructor and, if it implements MonadInstance as well, a monad.
|
||||
* Typically, it will be either a top-level module or a stable member of a top-level module (such as a val or a nested module).
|
||||
* The `with Singleton` part of the type verifies some cases at macro compilation time,
|
||||
* while the full check for static accessibility is done at macro expansion time.
|
||||
* Note: Ideally, the types would verify that `i: MonadInstance` when `t.isRight`.
|
||||
* With the various dependent types involved, this is not worth it.
|
||||
*
|
||||
* The `t` argument is the argument of the macro that will be transformed as described above.
|
||||
* If the macro that calls this method is for a multi-input map (app followed by map),
|
||||
* `t` should be the argument wrapped in Left.
|
||||
* If this is for multi-input flatMap (app followed by flatMap),
|
||||
* this should be the argument wrapped in Right.
|
||||
*/
|
||||
def contImpl[T, N[_]](
|
||||
c: blackbox.Context,
|
||||
i: Instance with Singleton,
|
||||
convert: Convert,
|
||||
builder: TupleBuilder,
|
||||
linter: LinterDSL
|
||||
)(
|
||||
t: Either[c.Expr[T], c.Expr[i.M[T]]],
|
||||
inner: Transform[c.type, N]
|
||||
)(
|
||||
implicit tt: c.WeakTypeTag[T],
|
||||
nt: c.WeakTypeTag[N[T]],
|
||||
it: c.TypeTag[i.type]
|
||||
): c.Expr[i.M[N[T]]] = {
|
||||
import c.universe.{ Apply => ApplyTree, _ }
|
||||
|
||||
val util = ContextUtil[c.type](c)
|
||||
val mTC: Type = util.extractTC(i, InstanceTCName)
|
||||
val mttpe: Type = appliedType(mTC, nt.tpe :: Nil).dealias
|
||||
|
||||
// the tree for the macro argument
|
||||
val (tree, treeType) = t match {
|
||||
case Left(l) => (l.tree, nt.tpe.dealias)
|
||||
case Right(r) => (r.tree, mttpe)
|
||||
}
|
||||
// the Symbol for the anonymous function passed to the appropriate Instance.map/flatMap/pure method
|
||||
// this Symbol needs to be known up front so that it can be used as the owner of synthetic vals
|
||||
val functionSym = util.functionSymbol(tree.pos)
|
||||
|
||||
val instanceSym = util.singleton(i)
|
||||
// A Tree that references the statically accessible Instance that provides the actual implementations of map, flatMap, ...
|
||||
val instance = Ident(instanceSym)
|
||||
|
||||
val isWrapper: (String, Type, Tree) => Boolean = convert.asPredicate(c)
|
||||
|
||||
// Local definitions `defs` in the macro. This is used to ensure references are to M instances defined outside of the macro call.
|
||||
// Also `refCount` is the number of references, which is used to create the private, synthetic method containing the body
|
||||
val defs = util.collectDefs(tree, isWrapper)
|
||||
val checkQual: Tree => Unit = util.checkReferences(defs, isWrapper, mttpe.erasure)
|
||||
|
||||
type In = Input[c.universe.type]
|
||||
var inputs = List[In]()
|
||||
|
||||
// transforms the original tree into calls to the Instance functions pure, map, ...,
|
||||
// resulting in a value of type M[T]
|
||||
def makeApp(body: Tree): Tree =
|
||||
inputs match {
|
||||
case Nil => pure(body)
|
||||
case x :: Nil => single(body, x)
|
||||
case xs => arbArity(body, xs)
|
||||
}
|
||||
|
||||
// no inputs, so construct M[T] via Instance.pure or pure+flatten
|
||||
def pure(body: Tree): Tree = {
|
||||
val typeApplied = TypeApply(util.select(instance, PureName), TypeTree(treeType) :: Nil)
|
||||
val f = util.createFunction(Nil, body, functionSym)
|
||||
val p = ApplyTree(typeApplied, f :: Nil)
|
||||
if (t.isLeft) p else flatten(p)
|
||||
}
|
||||
// m should have type M[M[T]]
|
||||
// the returned Tree will have type M[T]
|
||||
def flatten(m: Tree): Tree = {
|
||||
val typedFlatten = TypeApply(util.select(instance, FlattenName), TypeTree(tt.tpe) :: Nil)
|
||||
ApplyTree(typedFlatten, m :: Nil)
|
||||
}
|
||||
|
||||
// calls Instance.map or flatmap directly, skipping the intermediate Instance.app that is unnecessary for a single input
|
||||
def single(body: Tree, input: In): Tree = {
|
||||
val variable = input.local
|
||||
val param =
|
||||
treeCopy.ValDef(variable, util.parameterModifiers, variable.name, variable.tpt, EmptyTree)
|
||||
val typeApplied =
|
||||
TypeApply(util.select(instance, MapName), variable.tpt :: (TypeTree(treeType): Tree) :: Nil)
|
||||
val f = util.createFunction(param :: Nil, body, functionSym)
|
||||
val mapped = ApplyTree(typeApplied, input.expr :: f :: Nil)
|
||||
if (t.isLeft) mapped else flatten(mapped)
|
||||
}
|
||||
|
||||
// calls Instance.app to get the values for all inputs and then calls Instance.map or flatMap to evaluate the body
|
||||
def arbArity(body: Tree, inputs: List[In]): Tree = {
|
||||
val result = builder.make(c)(mTC, inputs)
|
||||
val param = util.freshMethodParameter(appliedType(result.representationC, util.idTC :: Nil))
|
||||
val bindings = result.extract(param)
|
||||
val f = util.createFunction(param :: Nil, Block(bindings, body), functionSym)
|
||||
val ttt = TypeTree(treeType)
|
||||
val typedApp =
|
||||
TypeApply(util.select(instance, ApplyName), TypeTree(result.representationC) :: ttt :: Nil)
|
||||
val app =
|
||||
ApplyTree(ApplyTree(typedApp, result.input :: f :: Nil), result.alistInstance :: Nil)
|
||||
if (t.isLeft) app else flatten(app)
|
||||
}
|
||||
|
||||
// Called when transforming the tree to add an input.
|
||||
// For `qual` of type M[A], and a `selection` qual.value,
|
||||
// the call is addType(Type A, Tree qual)
|
||||
// The result is a Tree representing a reference to
|
||||
// the bound value of the input.
|
||||
def addType(tpe: Type, qual: Tree, selection: Tree): Tree = {
|
||||
qual.foreach(checkQual)
|
||||
val vd = util.freshValDef(tpe, qual.pos, functionSym)
|
||||
inputs ::= new Input(tpe, qual, vd)
|
||||
util.refVal(selection, vd)
|
||||
}
|
||||
def sub(name: String, tpe: Type, qual: Tree, replace: Tree): Converted[c.type] = {
|
||||
val tag = c.WeakTypeTag[T](tpe)
|
||||
convert[T](c)(name, qual)(tag) transform { tree =>
|
||||
addType(tpe, tree, replace)
|
||||
}
|
||||
}
|
||||
|
||||
// applies the transformation
|
||||
linter.runLinter(c)(tree)
|
||||
val tx = util.transformWrappers(tree, (n, tpe, t, replace) => sub(n, tpe, t, replace))
|
||||
// resetting attributes must be: a) local b) done here and not wider or else there are obscure errors
|
||||
val tr = makeApp(inner(tx))
|
||||
val noWarn = q"""($tr: @_root_.scala.annotation.nowarn("cat=other-pure-statement"))"""
|
||||
c.Expr[i.M[N[T]]](noWarn)
|
||||
}
|
||||
|
||||
import Types._
|
||||
|
||||
implicit def applicativeInstance[A[_]](implicit ap: Applicative[A]): Instance.Aux[A] =
|
||||
new Instance {
|
||||
type M[x] = A[x]
|
||||
def app[K[L[x]], Z](in: K[A], f: K[Id] => Z)(implicit a: AList[K]) = a.apply[A, Z](in, f)
|
||||
def map[S, T](in: A[S], f: S => T) = ap.map(f, in)
|
||||
def pure[S](s: () => S): M[S] = ap.pure(s())
|
||||
}
|
||||
|
||||
def compose[A[_], B[_]](implicit a: Aux[A], b: Aux[B]): Instance.Aux2[A, B] =
|
||||
new Composed[A, B](a, b)
|
||||
// made a public, named, unsealed class because of trouble with macros and inference when the Instance is not an object
|
||||
class Composed[A[_], B[_]](a: Aux[A], b: Aux[B]) extends Instance {
|
||||
type M[x] = A[B[x]]
|
||||
def pure[S](s: () => S): A[B[S]] = a.pure(() => b.pure(s))
|
||||
def map[S, T](in: M[S], f: S => T): M[T] = a.map(in, (bv: B[S]) => b.map(bv, f))
|
||||
def app[K[L[x]], Z](in: K[M], f: K[Id] => Z)(implicit alist: AList[K]): A[B[Z]] = {
|
||||
val g: K[B] => B[Z] = in => b.app[K, Z](in, f)
|
||||
a.app[AList.SplitK[K, B]#l, B[Z]](in, g)(AList.asplit(alist))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import scala.reflect._
|
||||
import macros._
|
||||
|
||||
/** A `TupleBuilder` that uses a KList as the tuple representation.*/
|
||||
object KListBuilder extends TupleBuilder {
|
||||
def make(
|
||||
c: blackbox.Context
|
||||
)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] =
|
||||
new BuilderResult[c.type] {
|
||||
val ctx: c.type = c
|
||||
val util = ContextUtil[c.type](c)
|
||||
import c.universe.{ Apply => ApplyTree, _ }
|
||||
import util._
|
||||
|
||||
val knilType = c.typeOf[KNil]
|
||||
val knil = Ident(knilType.typeSymbol.companion)
|
||||
val kconsTpe = c.typeOf[KCons[Int, KNil, List]]
|
||||
val kcons = kconsTpe.typeSymbol.companion
|
||||
val mTC: Type = mt.asInstanceOf[c.universe.Type]
|
||||
val kconsTC: Type = kconsTpe.typeConstructor
|
||||
|
||||
/** This is the L in the type function [L[x]] ... */
|
||||
val tcVariable: TypeSymbol = newTCVariable(util.initialOwner)
|
||||
|
||||
/** Instantiates KCons[h, t <: KList[L], L], where L is the type constructor variable */
|
||||
def kconsType(h: Type, t: Type): Type =
|
||||
appliedType(kconsTC, h :: t :: refVar(tcVariable) :: Nil)
|
||||
|
||||
def bindKList(prev: ValDef, revBindings: List[ValDef], params: List[ValDef]): List[ValDef] =
|
||||
params match {
|
||||
case (x @ ValDef(mods, name, tpt, _)) :: xs =>
|
||||
val rhs = select(Ident(prev.name), "head")
|
||||
val head = treeCopy.ValDef(x, mods, name, tpt, rhs)
|
||||
util.setSymbol(head, x.symbol)
|
||||
val tail = localValDef(TypeTree(), select(Ident(prev.name), "tail"))
|
||||
val base = head :: revBindings
|
||||
bindKList(tail, if (xs.isEmpty) base else tail :: base, xs)
|
||||
case Nil => revBindings.reverse
|
||||
}
|
||||
|
||||
private[this] def makeKList(
|
||||
revInputs: Inputs[c.universe.type],
|
||||
klist: Tree,
|
||||
klistType: Type
|
||||
): Tree =
|
||||
revInputs match {
|
||||
case in :: tail =>
|
||||
val next = ApplyTree(
|
||||
TypeApply(
|
||||
Ident(kcons),
|
||||
TypeTree(in.tpe) :: TypeTree(klistType) :: TypeTree(mTC) :: Nil
|
||||
),
|
||||
in.expr :: klist :: Nil
|
||||
)
|
||||
makeKList(tail, next, appliedType(kconsTC, in.tpe :: klistType :: mTC :: Nil))
|
||||
case Nil => klist
|
||||
}
|
||||
|
||||
/** The input trees combined in a KList */
|
||||
val klist = makeKList(inputs.reverse, knil, knilType)
|
||||
|
||||
/**
|
||||
* The input types combined in a KList type. The main concern is tracking the heterogeneous types.
|
||||
* The type constructor is tcVariable, so that it can be applied to [X] X or M later.
|
||||
* When applied to `M`, this type gives the type of the `input` KList.
|
||||
*/
|
||||
val klistType: Type = inputs.foldRight(knilType)((in, klist) => kconsType(in.tpe, klist))
|
||||
|
||||
val representationC = internal.polyType(tcVariable :: Nil, klistType)
|
||||
val input = klist
|
||||
val alistInstance: ctx.universe.Tree =
|
||||
TypeApply(select(Ident(alist), "klist"), TypeTree(representationC) :: Nil)
|
||||
def extract(param: ValDef) = bindKList(param, Nil, inputs.map(_.local))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util.appmacro
|
||||
|
||||
import scala.reflect.macros.blackbox
|
||||
|
||||
trait LinterDSL {
|
||||
def runLinter(ctx: blackbox.Context)(tree: ctx.Tree): Unit
|
||||
}
|
||||
|
||||
object LinterDSL {
|
||||
object Empty extends LinterDSL {
|
||||
override def runLinter(ctx: blackbox.Context)(tree: ctx.Tree): Unit = ()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import scala.reflect._
|
||||
import macros._
|
||||
|
||||
/**
|
||||
* A builder that uses `TupleN` as the representation for small numbers of inputs (up to `TupleNBuilder.MaxInputs`)
|
||||
* and `KList` for larger numbers of inputs. This builder cannot handle fewer than 2 inputs.
|
||||
*/
|
||||
object MixedBuilder extends TupleBuilder {
|
||||
def make(
|
||||
c: blackbox.Context
|
||||
)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] = {
|
||||
val delegate =
|
||||
if (inputs.size > TupleNBuilder.MaxInputs) (KListBuilder: TupleBuilder)
|
||||
else (TupleNBuilder: TupleBuilder)
|
||||
delegate.make(c)(mt, inputs)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util.appmacro
|
||||
|
||||
import scala.reflect.macros.blackbox
|
||||
|
||||
object StringTypeTag {
|
||||
def impl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
|
||||
import c.universe._
|
||||
val tpe = weakTypeOf[A]
|
||||
def typeToString(tpe: Type): String = tpe match {
|
||||
case TypeRef(_, sym, args) if args.nonEmpty =>
|
||||
val typeCon = tpe.typeSymbol.fullName
|
||||
val typeArgs = args map typeToString
|
||||
s"""$typeCon[${typeArgs.mkString(",")}]"""
|
||||
case _ => tpe.toString
|
||||
}
|
||||
|
||||
val key = Literal(Constant(typeToString(tpe)))
|
||||
q"new sbt.internal.util.StringTypeTag[$tpe]($key)"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import scala.reflect._
|
||||
import macros._
|
||||
|
||||
/**
|
||||
* A `TupleBuilder` abstracts the work of constructing a tuple data structure such as a `TupleN` or `KList`
|
||||
* and extracting values from it. The `Instance` macro implementation will (roughly) traverse the tree of its argument
|
||||
* and ultimately obtain a list of expressions with type `M[T]` for different types `T`.
|
||||
* The macro constructs an `Input` value for each of these expressions that contains the `Type` for `T`,
|
||||
* the `Tree` for the expression, and a `ValDef` that will hold the value for the input.
|
||||
*
|
||||
* `TupleBuilder.apply` is provided with the list of `Input`s and is expected to provide three values in the returned BuilderResult.
|
||||
* First, it returns the constructed tuple data structure Tree in `input`.
|
||||
* Next, it provides the type constructor `representationC` that, when applied to M, gives the type of tuple data structure.
|
||||
* For example, a builder that constructs a `Tuple3` for inputs `M[Int]`, `M[Boolean]`, and `M[String]`
|
||||
* would provide a Type representing `[L[x]] (L[Int], L[Boolean], L[String])`. The `input` method
|
||||
* would return a value whose type is that type constructor applied to M, or `(M[Int], M[Boolean], M[String])`.
|
||||
*
|
||||
* Finally, the `extract` method provides a list of vals that extract information from the applied input.
|
||||
* The type of the applied input is the type constructor applied to `Id` (`[X] X`).
|
||||
* The returned list of ValDefs should be the ValDefs from `inputs`, but with non-empty right-hand sides.
|
||||
*/
|
||||
trait TupleBuilder {
|
||||
|
||||
/** A convenience alias for a list of inputs (associated with a Universe of type U). */
|
||||
type Inputs[U <: Universe with Singleton] = List[Instance.Input[U]]
|
||||
|
||||
/** Constructs a one-time use Builder for Context `c` and type constructor `tcType`. */
|
||||
def make(
|
||||
c: blackbox.Context
|
||||
)(tcType: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type]
|
||||
}
|
||||
|
||||
trait BuilderResult[C <: blackbox.Context with Singleton] {
|
||||
val ctx: C
|
||||
import ctx.universe._
|
||||
|
||||
/**
|
||||
* Represents the higher-order type constructor `[L[x]] ...` where `...` is the
|
||||
* type of the data structure containing the added expressions,
|
||||
* except that it is abstracted over the type constructor applied to each heterogeneous part of the type .
|
||||
*/
|
||||
def representationC: PolyType
|
||||
|
||||
/** The instance of AList for the input. For a `representationC` of `[L[x]]`, this `Tree` should have a `Type` of `AList[L]`*/
|
||||
def alistInstance: Tree
|
||||
|
||||
/** Returns the completed value containing all expressions added to the builder. */
|
||||
def input: Tree
|
||||
|
||||
/* The list of definitions that extract values from a value of type `$representationC[Id]`.
|
||||
* The returned value should be identical to the `ValDef`s provided to the `TupleBuilder.make` method but with
|
||||
* non-empty right hand sides. Each `ValDef` may refer to `param` and previous `ValDef`s in the list.*/
|
||||
def extract(param: ValDef): List[ValDef]
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package appmacro
|
||||
|
||||
import scala.tools.nsc.Global
|
||||
import scala.reflect._
|
||||
import macros._
|
||||
|
||||
/**
|
||||
* A builder that uses a TupleN as the tuple representation.
|
||||
* It is limited to tuples of size 2 to `MaxInputs`.
|
||||
*/
|
||||
object TupleNBuilder extends TupleBuilder {
|
||||
|
||||
/** The largest number of inputs that this builder can handle. */
|
||||
final val MaxInputs = 11
|
||||
final val TupleMethodName = "tuple"
|
||||
|
||||
def make(
|
||||
c: blackbox.Context
|
||||
)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] =
|
||||
new BuilderResult[c.type] {
|
||||
val util = ContextUtil[c.type](c)
|
||||
import c.universe._
|
||||
import util._
|
||||
|
||||
val global: Global = c.universe.asInstanceOf[Global]
|
||||
|
||||
val ctx: c.type = c
|
||||
val representationC: PolyType = {
|
||||
val tcVariable: Symbol = newTCVariable(util.initialOwner)
|
||||
val tupleTypeArgs = inputs.map(
|
||||
in => internal.typeRef(NoPrefix, tcVariable, in.tpe :: Nil).asInstanceOf[global.Type]
|
||||
)
|
||||
val tuple = global.definitions.tupleType(tupleTypeArgs)
|
||||
internal.polyType(tcVariable :: Nil, tuple.asInstanceOf[Type])
|
||||
}
|
||||
|
||||
val input: Tree = mkTuple(inputs.map(_.expr))
|
||||
val alistInstance: Tree = {
|
||||
val selectTree = select(Ident(alist), TupleMethodName + inputs.size.toString)
|
||||
TypeApply(selectTree, inputs.map(in => TypeTree(in.tpe)))
|
||||
}
|
||||
def extract(param: ValDef): List[ValDef] = bindTuple(param, Nil, inputs.map(_.local), 1)
|
||||
|
||||
def bindTuple(
|
||||
param: ValDef,
|
||||
revBindings: List[ValDef],
|
||||
params: List[ValDef],
|
||||
i: Int
|
||||
): List[ValDef] =
|
||||
params match {
|
||||
case (x @ ValDef(mods, name, tpt, _)) :: xs =>
|
||||
val rhs = select(Ident(param.name), "_" + i.toString)
|
||||
val newVal = treeCopy.ValDef(x, mods, name, tpt, rhs)
|
||||
util.setSymbol(newVal, x.symbol)
|
||||
bindTuple(param, newVal :: revBindings, xs, i + 1)
|
||||
case Nil => revBindings.reverse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package plugins
|
||||
|
||||
object DependencyTreePlugin extends AutoPlugin {
|
||||
object autoImport extends DependencyTreeKeys
|
||||
override def trigger = AllRequirements
|
||||
override def requires = MiniDependencyTreePlugin
|
||||
|
||||
val configurations = Vector(Compile, Test, IntegrationTest, Runtime, Provided, Optional)
|
||||
|
||||
// MiniDependencyTreePlugin provides baseBasicReportingSettings for Compile and Test
|
||||
override def projectSettings: Seq[Def.Setting[_]] =
|
||||
((configurations diff Vector(Compile, Test)) flatMap { config =>
|
||||
inConfig(config)(DependencyTreeSettings.baseBasicReportingSettings)
|
||||
}) ++
|
||||
(configurations flatMap { config =>
|
||||
inConfig(config)(DependencyTreeSettings.baseFullReportingSettings)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
sbt: Collection Component
|
||||
Copyright 2011 - 2017, Lightbend, Inc.
|
||||
Copyright 2008 - 2010, Mark Harrah
|
||||
Licensed under BSD-3-Clause license (see LICENSE)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
import scala.collection.parallel.ParSeq
|
||||
|
||||
private[util] object Par {
|
||||
def apply[R](s: Seq[R]): ParSeq[R] = s.par
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
private[util] class WrappedMap[K, V](val jmap: java.util.Map[K, V]) extends Map[K, V] {
|
||||
def +[V1 >: V](kv: (K, V1)): scala.collection.immutable.Map[K, V1] =
|
||||
jmap.asScala.toMap + kv
|
||||
def -(key: K): scala.collection.immutable.Map[K, V] = jmap.asScala.toMap - key
|
||||
def get(key: K): Option[V] = Option(jmap.get(key))
|
||||
def iterator: Iterator[(K, V)] = jmap.asScala.iterator
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
import scala.collection.parallel.CollectionConverters._
|
||||
import scala.collection.parallel.ParSeq
|
||||
|
||||
private[util] object Par {
|
||||
def apply[R](s: Seq[R]): ParSeq[R] = s.par
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
private[util] class WrappedMap[K, V](val jmap: java.util.Map[K, V]) extends Map[K, V] {
|
||||
def removed(key: K): scala.collection.immutable.Map[K, V] = jmap.asScala.toMap.removed(key)
|
||||
def updated[V1 >: V](key: K, value: V1): scala.collection.immutable.Map[K, V1] =
|
||||
jmap.asScala.toMap.updated(key, value)
|
||||
|
||||
def get(key: K): Option[V] = Option(jmap.get(key))
|
||||
def iterator: Iterator[(K, V)] = jmap.asScala.iterator
|
||||
}
|
||||
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import Classes.Applicative
|
||||
import Types._
|
||||
|
||||
/**
|
||||
* An abstraction over a higher-order type constructor `K[x[y]]` with the purpose of abstracting
|
||||
* over heterogeneous sequences like `KList` and `TupleN` with elements with a common type
|
||||
* constructor as well as homogeneous sequences `Seq[M[T]]`.
|
||||
*/
|
||||
trait AList[K[L[x]]] {
|
||||
def transform[M[_], N[_]](value: K[M], f: M ~> N): K[N]
|
||||
def traverse[M[_], N[_], P[_]](value: K[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[K[P]]
|
||||
def foldr[M[_], A](value: K[M], f: (M[_], A) => A, init: A): A
|
||||
|
||||
def toList[M[_]](value: K[M]): List[M[_]] = foldr[M, List[M[_]]](value, _ :: _, Nil)
|
||||
|
||||
def apply[M[_], C](value: K[M], f: K[Id] => C)(implicit a: Applicative[M]): M[C] =
|
||||
a.map(f, traverse[M, M, Id](value, idK[M])(a))
|
||||
}
|
||||
|
||||
object AList {
|
||||
type Empty = AList[ConstK[Unit]#l]
|
||||
|
||||
/** AList for Unit, which represents a sequence that is always empty.*/
|
||||
val empty: Empty = new Empty {
|
||||
def transform[M[_], N[_]](in: Unit, f: M ~> N) = ()
|
||||
def foldr[M[_], T](in: Unit, f: (M[_], T) => T, init: T) = init
|
||||
override def apply[M[_], C](in: Unit, f: Unit => C)(implicit app: Applicative[M]): M[C] =
|
||||
app.pure(f(()))
|
||||
def traverse[M[_], N[_], P[_]](in: Unit, f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[Unit] = np.pure(())
|
||||
}
|
||||
|
||||
type SeqList[T] = AList[λ[L[x] => List[L[T]]]]
|
||||
|
||||
/** AList for a homogeneous sequence. */
|
||||
def seq[T]: SeqList[T] = new SeqList[T] {
|
||||
def transform[M[_], N[_]](s: List[M[T]], f: M ~> N) = s.map(f.fn[T])
|
||||
def foldr[M[_], A](s: List[M[T]], f: (M[_], A) => A, init: A): A =
|
||||
s.reverse.foldLeft(init)((t, m) => f(m, t))
|
||||
|
||||
override def apply[M[_], C](s: List[M[T]], f: List[T] => C)(
|
||||
implicit ap: Applicative[M]
|
||||
): M[C] = {
|
||||
def loop[V](in: List[M[T]], g: List[T] => V): M[V] =
|
||||
in match {
|
||||
case Nil => ap.pure(g(Nil))
|
||||
case x :: xs =>
|
||||
val h = (ts: List[T]) => (t: T) => g(t :: ts)
|
||||
ap.apply(loop(xs, h), x)
|
||||
}
|
||||
loop(s, f)
|
||||
}
|
||||
|
||||
def traverse[M[_], N[_], P[_]](s: List[M[T]], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[List[P[T]]] = ???
|
||||
}
|
||||
|
||||
/** AList for the arbitrary arity data structure KList. */
|
||||
def klist[KL[M[_]] <: KList.Aux[M, KL]]: AList[KL] = new AList[KL] {
|
||||
def transform[M[_], N[_]](k: KL[M], f: M ~> N) = k.transform(f)
|
||||
def foldr[M[_], T](k: KL[M], f: (M[_], T) => T, init: T): T = k.foldr(f, init)
|
||||
override def apply[M[_], C](k: KL[M], f: KL[Id] => C)(implicit app: Applicative[M]): M[C] =
|
||||
k.apply(f)(app)
|
||||
def traverse[M[_], N[_], P[_]](k: KL[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[KL[P]] = k.traverse[N, P](f)(np)
|
||||
override def toList[M[_]](k: KL[M]) = k.toList
|
||||
}
|
||||
|
||||
type Single[A] = AList[λ[L[x] => L[A]]]
|
||||
|
||||
/** AList for a single value. */
|
||||
def single[A]: Single[A] = new Single[A] {
|
||||
def transform[M[_], N[_]](a: M[A], f: M ~> N) = f(a)
|
||||
def foldr[M[_], T](a: M[A], f: (M[_], T) => T, init: T): T = f(a, init)
|
||||
def traverse[M[_], N[_], P[_]](a: M[A], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[P[A]] = f(a)
|
||||
}
|
||||
|
||||
/** Example: calling `AList.SplitK[K, Task]#l` returns the type lambda `A[x] => K[A[Task[x]]`. */
|
||||
sealed trait SplitK[K[L[x]], B[x]] { type l[A[x]] = K[(A ∙ B)#l] }
|
||||
|
||||
type ASplit[K[L[x]], B[x]] = AList[SplitK[K, B]#l]
|
||||
|
||||
/** AList that operates on the outer type constructor `A` of a composition `[x] A[B[x]]` for type constructors `A` and `B`. */
|
||||
def asplit[K[L[x]], B[x]](base: AList[K]): ASplit[K, B] = new ASplit[K, B] {
|
||||
type Split[L[x]] = K[(L ∙ B)#l]
|
||||
|
||||
def transform[M[_], N[_]](value: Split[M], f: M ~> N): Split[N] =
|
||||
base.transform[(M ∙ B)#l, (N ∙ B)#l](value, nestCon[M, N, B](f))
|
||||
|
||||
def traverse[M[_], N[_], P[_]](value: Split[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[Split[P]] = {
|
||||
val g = nestCon[M, (N ∙ P)#l, B](f)
|
||||
base.traverse[(M ∙ B)#l, N, (P ∙ B)#l](value, g)(np)
|
||||
}
|
||||
|
||||
def foldr[M[_], A](value: Split[M], f: (M[_], A) => A, init: A): A =
|
||||
base.foldr[(M ∙ B)#l, A](value, f, init)
|
||||
}
|
||||
|
||||
// TODO: auto-generate
|
||||
sealed trait T2K[A, B] { type l[L[x]] = (L[A], L[B]) }
|
||||
type T2List[A, B] = AList[T2K[A, B]#l]
|
||||
def tuple2[A, B]: T2List[A, B] = new T2List[A, B] {
|
||||
type T2[M[_]] = (M[A], M[B])
|
||||
def transform[M[_], N[_]](t: T2[M], f: M ~> N): T2[N] = (f(t._1), f(t._2))
|
||||
def foldr[M[_], T](t: T2[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, init))
|
||||
def traverse[M[_], N[_], P[_]](t: T2[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T2[P]] = {
|
||||
val g = (Tuple2.apply[P[A], P[B]] _).curried
|
||||
np.apply(np.map(g, f(t._1)), f(t._2))
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T3K[A, B, C] { type l[L[x]] = (L[A], L[B], L[C]) }
|
||||
type T3List[A, B, C] = AList[T3K[A, B, C]#l]
|
||||
def tuple3[A, B, C]: T3List[A, B, C] = new T3List[A, B, C] {
|
||||
type T3[M[_]] = (M[A], M[B], M[C])
|
||||
def transform[M[_], N[_]](t: T3[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3))
|
||||
def foldr[M[_], T](t: T3[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, init)))
|
||||
def traverse[M[_], N[_], P[_]](t: T3[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T3[P]] = {
|
||||
val g = (Tuple3.apply[P[A], P[B], P[C]] _).curried
|
||||
np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3))
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T4K[A, B, C, D] { type l[L[x]] = (L[A], L[B], L[C], L[D]) }
|
||||
type T4List[A, B, C, D] = AList[T4K[A, B, C, D]#l]
|
||||
def tuple4[A, B, C, D]: T4List[A, B, C, D] = new T4List[A, B, C, D] {
|
||||
type T4[M[_]] = (M[A], M[B], M[C], M[D])
|
||||
def transform[M[_], N[_]](t: T4[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4))
|
||||
def foldr[M[_], T](t: T4[M], f: (M[_], T) => T, init: T): T =
|
||||
f(t._1, f(t._2, f(t._3, f(t._4, init))))
|
||||
def traverse[M[_], N[_], P[_]](t: T4[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T4[P]] = {
|
||||
val g = (Tuple4.apply[P[A], P[B], P[C], P[D]] _).curried
|
||||
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4))
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T5K[A, B, C, D, E] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E]) }
|
||||
type T5List[A, B, C, D, E] = AList[T5K[A, B, C, D, E]#l]
|
||||
def tuple5[A, B, C, D, E]: T5List[A, B, C, D, E] = new T5List[A, B, C, D, E] {
|
||||
type T5[M[_]] = (M[A], M[B], M[C], M[D], M[E])
|
||||
def transform[M[_], N[_]](t: T5[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5))
|
||||
def foldr[M[_], T](t: T5[M], f: (M[_], T) => T, init: T): T =
|
||||
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, init)))))
|
||||
def traverse[M[_], N[_], P[_]](t: T5[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T5[P]] = {
|
||||
val g = (Tuple5.apply[P[A], P[B], P[C], P[D], P[E]] _).curried
|
||||
np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5))
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T6K[A, B, C, D, E, F] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F]) }
|
||||
type T6List[A, B, C, D, E, F] = AList[T6K[A, B, C, D, E, F]#l]
|
||||
def tuple6[A, B, C, D, E, F]: T6List[A, B, C, D, E, F] = new T6List[A, B, C, D, E, F] {
|
||||
type T6[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F])
|
||||
def transform[M[_], N[_]](t: T6[M], f: M ~> N) =
|
||||
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6))
|
||||
def foldr[M[_], T](t: T6[M], f: (M[_], T) => T, init: T): T =
|
||||
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, init))))))
|
||||
def traverse[M[_], N[_], P[_]](t: T6[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T6[P]] = {
|
||||
val g = (Tuple6.apply[P[A], P[B], P[C], P[D], P[E], P[F]] _).curried
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
|
||||
f(t._5)
|
||||
),
|
||||
f(t._6)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T7K[A, B, C, D, E, F, G] {
|
||||
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G])
|
||||
}
|
||||
type T7List[A, B, C, D, E, F, G] = AList[T7K[A, B, C, D, E, F, G]#l]
|
||||
def tuple7[A, B, C, D, E, F, G]: T7List[A, B, C, D, E, F, G] = new T7List[A, B, C, D, E, F, G] {
|
||||
type T7[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G])
|
||||
def transform[M[_], N[_]](t: T7[M], f: M ~> N) =
|
||||
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7))
|
||||
def foldr[M[_], T](t: T7[M], f: (M[_], T) => T, init: T): T =
|
||||
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, init)))))))
|
||||
def traverse[M[_], N[_], P[_]](t: T7[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T7[P]] = {
|
||||
val g = (Tuple7.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G]] _).curried
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
|
||||
f(t._5)
|
||||
),
|
||||
f(t._6)
|
||||
),
|
||||
f(t._7)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T8K[A, B, C, D, E, F, G, H] {
|
||||
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H])
|
||||
}
|
||||
type T8List[A, B, C, D, E, F, G, H] = AList[T8K[A, B, C, D, E, F, G, H]#l]
|
||||
def tuple8[A, B, C, D, E, F, G, H]: T8List[A, B, C, D, E, F, G, H] =
|
||||
new T8List[A, B, C, D, E, F, G, H] {
|
||||
type T8[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H])
|
||||
def transform[M[_], N[_]](t: T8[M], f: M ~> N) =
|
||||
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8))
|
||||
def foldr[M[_], T](t: T8[M], f: (M[_], T) => T, init: T): T =
|
||||
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, init))))))))
|
||||
def traverse[M[_], N[_], P[_]](t: T8[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T8[P]] = {
|
||||
val g = (Tuple8.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H]] _).curried
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
|
||||
f(t._5)
|
||||
),
|
||||
f(t._6)
|
||||
),
|
||||
f(t._7)
|
||||
),
|
||||
f(t._8)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T9K[A, B, C, D, E, F, G, H, I] {
|
||||
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I])
|
||||
}
|
||||
type T9List[A, B, C, D, E, F, G, H, I] = AList[T9K[A, B, C, D, E, F, G, H, I]#l]
|
||||
def tuple9[A, B, C, D, E, F, G, H, I]: T9List[A, B, C, D, E, F, G, H, I] =
|
||||
new T9List[A, B, C, D, E, F, G, H, I] {
|
||||
type T9[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I])
|
||||
def transform[M[_], N[_]](t: T9[M], f: M ~> N) =
|
||||
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9))
|
||||
def foldr[M[_], T](t: T9[M], f: (M[_], T) => T, init: T): T =
|
||||
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, init)))))))))
|
||||
def traverse[M[_], N[_], P[_]](t: T9[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T9[P]] = {
|
||||
val g = (Tuple9.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I]] _).curried
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
|
||||
f(t._5)
|
||||
),
|
||||
f(t._6)
|
||||
),
|
||||
f(t._7)
|
||||
),
|
||||
f(t._8)
|
||||
),
|
||||
f(t._9)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T10K[A, B, C, D, E, F, G, H, I, J] {
|
||||
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J])
|
||||
}
|
||||
type T10List[A, B, C, D, E, F, G, H, I, J] = AList[T10K[A, B, C, D, E, F, G, H, I, J]#l]
|
||||
def tuple10[A, B, C, D, E, F, G, H, I, J]: T10List[A, B, C, D, E, F, G, H, I, J] =
|
||||
new T10List[A, B, C, D, E, F, G, H, I, J] {
|
||||
type T10[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J])
|
||||
def transform[M[_], N[_]](t: T10[M], f: M ~> N) =
|
||||
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9), f(t._10))
|
||||
def foldr[M[_], T](t: T10[M], f: (M[_], T) => T, init: T): T =
|
||||
f(
|
||||
t._1,
|
||||
f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, init)))))))))
|
||||
)
|
||||
def traverse[M[_], N[_], P[_]](t: T10[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T10[P]] = {
|
||||
val g =
|
||||
(Tuple10.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J]] _).curried
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
|
||||
f(t._5)
|
||||
),
|
||||
f(t._6)
|
||||
),
|
||||
f(t._7)
|
||||
),
|
||||
f(t._8)
|
||||
),
|
||||
f(t._9)
|
||||
),
|
||||
f(t._10)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait T11K[A, B, C, D, E, F, G, H, I, J, K] {
|
||||
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J], L[K])
|
||||
}
|
||||
type T11List[A, B, C, D, E, F, G, H, I, J, K] = AList[T11K[A, B, C, D, E, F, G, H, I, J, K]#l]
|
||||
def tuple11[A, B, C, D, E, F, G, H, I, J, K]: T11List[A, B, C, D, E, F, G, H, I, J, K] =
|
||||
new T11List[A, B, C, D, E, F, G, H, I, J, K] {
|
||||
type T11[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J], M[K])
|
||||
def transform[M[_], N[_]](t: T11[M], f: M ~> N) =
|
||||
(
|
||||
f(t._1),
|
||||
f(t._2),
|
||||
f(t._3),
|
||||
f(t._4),
|
||||
f(t._5),
|
||||
f(t._6),
|
||||
f(t._7),
|
||||
f(t._8),
|
||||
f(t._9),
|
||||
f(t._10),
|
||||
f(t._11)
|
||||
)
|
||||
def foldr[M[_], T](t: T11[M], f: (M[_], T) => T, init: T): T =
|
||||
f(
|
||||
t._1,
|
||||
f(
|
||||
t._2,
|
||||
f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, f(t._11, init)))))))))
|
||||
)
|
||||
)
|
||||
def traverse[M[_], N[_], P[_]](t: T11[M], f: M ~> (N ∙ P)#l)(
|
||||
implicit np: Applicative[N]
|
||||
): N[T11[P]] = {
|
||||
val g = (Tuple11
|
||||
.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J], P[K]] _).curried
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(
|
||||
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
|
||||
f(t._5)
|
||||
),
|
||||
f(t._6)
|
||||
),
|
||||
f(t._7)
|
||||
),
|
||||
f(t._8)
|
||||
),
|
||||
f(t._9)
|
||||
),
|
||||
f(t._10)
|
||||
),
|
||||
f(t._11)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import Types._
|
||||
import scala.reflect.Manifest
|
||||
import sbt.util.OptJsonWriter
|
||||
|
||||
// T must be invariant to work properly.
|
||||
// Because it is sealed and the only instances go through AttributeKey.apply,
|
||||
// a single AttributeKey instance cannot conform to AttributeKey[T] for different Ts
|
||||
|
||||
/**
|
||||
* A key in an [[AttributeMap]] that constrains its associated value to be of type `T`.
|
||||
* The key is uniquely defined by its `label` and type `T`, represented at runtime by `manifest`.
|
||||
*/
|
||||
sealed trait AttributeKey[T] {
|
||||
|
||||
/** The runtime evidence for `T`. */
|
||||
def manifest: Manifest[T]
|
||||
|
||||
/** The label is the identifier for the key and is camelCase by convention. */
|
||||
def label: String
|
||||
|
||||
/** An optional, brief description of the key. */
|
||||
def description: Option[String]
|
||||
|
||||
/**
|
||||
* In environments that support delegation, looking up this key when it has no associated value
|
||||
* will delegate to the values associated with these keys.
|
||||
* The delegation proceeds in order the keys are returned here.
|
||||
*/
|
||||
def extend: Seq[AttributeKey[_]]
|
||||
|
||||
/**
|
||||
* Specifies whether this key is a local, anonymous key (`true`) or not (`false`).
|
||||
* This is typically only used for programmatic, intermediate keys that should not be referenced outside of a specific scope.
|
||||
*/
|
||||
def isLocal: Boolean
|
||||
|
||||
/** Identifies the relative importance of a key among other keys.*/
|
||||
def rank: Int
|
||||
|
||||
def optJsonWriter: OptJsonWriter[T]
|
||||
|
||||
}
|
||||
|
||||
private[sbt] abstract class SharedAttributeKey[T] extends AttributeKey[T] {
|
||||
override final def toString = label
|
||||
override final def hashCode = label.hashCode
|
||||
override final def equals(o: Any) =
|
||||
(this eq o.asInstanceOf[AnyRef]) || (o match {
|
||||
case a: SharedAttributeKey[t] => a.label == this.label && a.manifest == this.manifest
|
||||
case _ => false
|
||||
})
|
||||
final def isLocal: Boolean = false
|
||||
}
|
||||
|
||||
object AttributeKey {
|
||||
def apply[T: Manifest: OptJsonWriter](name: String): AttributeKey[T] =
|
||||
make(name, None, Nil, Int.MaxValue)
|
||||
|
||||
def apply[T: Manifest: OptJsonWriter](name: String, rank: Int): AttributeKey[T] =
|
||||
make(name, None, Nil, rank)
|
||||
|
||||
def apply[T: Manifest: OptJsonWriter](name: String, description: String): AttributeKey[T] =
|
||||
apply(name, description, Nil)
|
||||
|
||||
def apply[T: Manifest: OptJsonWriter](
|
||||
name: String,
|
||||
description: String,
|
||||
rank: Int
|
||||
): AttributeKey[T] =
|
||||
apply(name, description, Nil, rank)
|
||||
|
||||
def apply[T: Manifest: OptJsonWriter](
|
||||
name: String,
|
||||
description: String,
|
||||
extend: Seq[AttributeKey[_]]
|
||||
): AttributeKey[T] =
|
||||
apply(name, description, extend, Int.MaxValue)
|
||||
|
||||
def apply[T: Manifest: OptJsonWriter](
|
||||
name: String,
|
||||
description: String,
|
||||
extend: Seq[AttributeKey[_]],
|
||||
rank: Int
|
||||
): AttributeKey[T] =
|
||||
make(name, Some(description), extend, rank)
|
||||
|
||||
private[sbt] def copyWithRank[T](a: AttributeKey[T], rank: Int): AttributeKey[T] =
|
||||
make(a.label, a.description, a.extend, rank)(a.manifest, a.optJsonWriter)
|
||||
|
||||
private[this] def make[T](
|
||||
name: String,
|
||||
description0: Option[String],
|
||||
extend0: Seq[AttributeKey[_]],
|
||||
rank0: Int
|
||||
)(implicit mf: Manifest[T], ojw: OptJsonWriter[T]): AttributeKey[T] =
|
||||
new SharedAttributeKey[T] {
|
||||
require(
|
||||
name.headOption.exists(_.isLower),
|
||||
s"A named attribute key must start with a lowercase letter: $name"
|
||||
)
|
||||
|
||||
def manifest = mf
|
||||
val label = Util.hyphenToCamel(name)
|
||||
def description = description0
|
||||
def extend = extend0
|
||||
def rank = rank0
|
||||
def optJsonWriter = ojw
|
||||
}
|
||||
|
||||
private[sbt] def local[T](implicit mf: Manifest[T], ojw: OptJsonWriter[T]): AttributeKey[T] =
|
||||
new AttributeKey[T] {
|
||||
def manifest = mf
|
||||
def label = LocalLabel
|
||||
def description = None
|
||||
def extend = Nil
|
||||
override def toString = label
|
||||
def isLocal: Boolean = true
|
||||
def rank = Int.MaxValue
|
||||
val optJsonWriter = ojw
|
||||
}
|
||||
|
||||
private[sbt] final val LocalLabel = "$" + "local"
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable map where a key is the tuple `(String,T)` for a fixed type `T` and can only be associated with values of type `T`.
|
||||
* It is therefore possible for this map to contain mappings for keys with the same label but different types.
|
||||
* Excluding this possibility is the responsibility of the client if desired.
|
||||
*/
|
||||
trait AttributeMap {
|
||||
|
||||
/**
|
||||
* Gets the value of type `T` associated with the key `k`.
|
||||
* If a key with the same label but different type is defined, this method will fail.
|
||||
*/
|
||||
def apply[T](k: AttributeKey[T]): T
|
||||
|
||||
/**
|
||||
* Gets the value of type `T` associated with the key `k` or `None` if no value is associated.
|
||||
* If a key with the same label but a different type is defined, this method will return `None`.
|
||||
*/
|
||||
def get[T](k: AttributeKey[T]): Option[T]
|
||||
|
||||
/**
|
||||
* Returns this map without the mapping for `k`.
|
||||
* This method will not remove a mapping for a key with the same label but a different type.
|
||||
*/
|
||||
def remove[T](k: AttributeKey[T]): AttributeMap
|
||||
|
||||
/**
|
||||
* Returns true if this map contains a mapping for `k`.
|
||||
* If a key with the same label but a different type is defined in this map, this method will return `false`.
|
||||
*/
|
||||
def contains[T](k: AttributeKey[T]): Boolean
|
||||
|
||||
/**
|
||||
* Adds the mapping `k -> value` to this map, replacing any existing mapping for `k`.
|
||||
* Any mappings for keys with the same label but different types are unaffected.
|
||||
*/
|
||||
def put[T](k: AttributeKey[T], value: T): AttributeMap
|
||||
|
||||
/** All keys with defined mappings. There may be multiple keys with the same `label`, but different types. */
|
||||
def keys: Iterable[AttributeKey[_]]
|
||||
|
||||
/** Adds the mappings in `o` to this map, with mappings in `o` taking precedence over existing mappings.*/
|
||||
def ++(o: Iterable[AttributeEntry[_]]): AttributeMap
|
||||
|
||||
/** Combines the mappings in `o` with the mappings in this map, with mappings in `o` taking precedence over existing mappings.*/
|
||||
def ++(o: AttributeMap): AttributeMap
|
||||
|
||||
/** All mappings in this map. The [[AttributeEntry]] type preserves the typesafety of mappings, although the specific types are unknown.*/
|
||||
def entries: Iterable[AttributeEntry[_]]
|
||||
|
||||
/** `true` if there are no mappings in this map, `false` if there are. */
|
||||
def isEmpty: Boolean
|
||||
|
||||
/**
|
||||
* Adds the mapping `k -> opt.get` if opt is Some.
|
||||
* Otherwise, it returns this map without the mapping for `k`.
|
||||
*/
|
||||
private[sbt] def setCond[T](k: AttributeKey[T], opt: Option[T]): AttributeMap
|
||||
}
|
||||
|
||||
object AttributeMap {
|
||||
|
||||
/** An [[AttributeMap]] without any mappings. */
|
||||
val empty: AttributeMap = new BasicAttributeMap(Map.empty)
|
||||
|
||||
/** Constructs an [[AttributeMap]] containing the given `entries`. */
|
||||
def apply(entries: Iterable[AttributeEntry[_]]): AttributeMap = empty ++ entries
|
||||
|
||||
/** Constructs an [[AttributeMap]] containing the given `entries`.*/
|
||||
def apply(entries: AttributeEntry[_]*): AttributeMap = empty ++ entries
|
||||
|
||||
/** Presents an `AttributeMap` as a natural transformation. */
|
||||
implicit def toNatTrans(map: AttributeMap): AttributeKey ~> Id = λ[AttributeKey ~> Id](map(_))
|
||||
}
|
||||
|
||||
private class BasicAttributeMap(private val backing: Map[AttributeKey[_], Any])
|
||||
extends AttributeMap {
|
||||
|
||||
def isEmpty: Boolean = backing.isEmpty
|
||||
def apply[T](k: AttributeKey[T]) = backing(k).asInstanceOf[T]
|
||||
def get[T](k: AttributeKey[T]) = backing.get(k).asInstanceOf[Option[T]]
|
||||
def remove[T](k: AttributeKey[T]): AttributeMap = new BasicAttributeMap(backing - k)
|
||||
def contains[T](k: AttributeKey[T]) = backing.contains(k)
|
||||
|
||||
def put[T](k: AttributeKey[T], value: T): AttributeMap =
|
||||
new BasicAttributeMap(backing.updated(k, value: Any))
|
||||
|
||||
def keys: Iterable[AttributeKey[_]] = backing.keys
|
||||
|
||||
def ++(o: Iterable[AttributeEntry[_]]): AttributeMap =
|
||||
new BasicAttributeMap(o.foldLeft(backing)((b, e) => b.updated(e.key, e.value: Any)))
|
||||
|
||||
def ++(o: AttributeMap): AttributeMap = o match {
|
||||
case bam: BasicAttributeMap =>
|
||||
new BasicAttributeMap(Map(backing.toSeq ++ bam.backing.toSeq: _*))
|
||||
case _ => o ++ this
|
||||
}
|
||||
|
||||
def entries: Iterable[AttributeEntry[_]] =
|
||||
backing.collect {
|
||||
case (k: AttributeKey[kt], v) => AttributeEntry(k, v.asInstanceOf[kt])
|
||||
}
|
||||
|
||||
private[sbt] def setCond[T](k: AttributeKey[T], opt: Option[T]): AttributeMap =
|
||||
opt match {
|
||||
case Some(v) => put(k, v)
|
||||
case None => remove(k)
|
||||
}
|
||||
|
||||
override def toString = entries.mkString("(", ", ", ")")
|
||||
}
|
||||
|
||||
// type inference required less generality
|
||||
/** A map entry where `key` is constrained to only be associated with a fixed value of type `T`. */
|
||||
final case class AttributeEntry[T](key: AttributeKey[T], value: T) {
|
||||
override def toString = key.label + ": " + value
|
||||
}
|
||||
|
||||
/** Associates a `metadata` map with `data`. */
|
||||
final case class Attributed[D](data: D)(val metadata: AttributeMap) {
|
||||
|
||||
/** Retrieves the associated value of `key` from the metadata. */
|
||||
def get[T](key: AttributeKey[T]): Option[T] = metadata.get(key)
|
||||
|
||||
/** Defines a mapping `key -> value` in the metadata. */
|
||||
def put[T](key: AttributeKey[T], value: T): Attributed[D] =
|
||||
Attributed(data)(metadata.put(key, value))
|
||||
|
||||
/** Transforms the data by applying `f`. */
|
||||
def map[T](f: D => T): Attributed[T] = Attributed(f(data))(metadata)
|
||||
|
||||
}
|
||||
|
||||
object Attributed {
|
||||
|
||||
/** Extracts the underlying data from the sequence `in`. */
|
||||
def data[T](in: Seq[Attributed[T]]): Seq[T] = in.map(_.data)
|
||||
|
||||
/** Associates empty metadata maps with each entry of `in`.*/
|
||||
def blankSeq[T](in: Seq[T]): Seq[Attributed[T]] = in map blank
|
||||
|
||||
/** Associates an empty metadata map with `data`. */
|
||||
def blank[T](data: T): Attributed[T] = Attributed(data)(AttributeMap.empty)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
object Classes {
|
||||
trait Applicative[M[_]] {
|
||||
def apply[S, T](f: M[S => T], v: M[S]): M[T]
|
||||
def pure[S](s: => S): M[S]
|
||||
def map[S, T](f: S => T, v: M[S]): M[T]
|
||||
}
|
||||
|
||||
trait Selective[M[_]] extends Applicative[M] {
|
||||
def select[A, B](fab: M[Either[A, B]])(fn: M[A => B]): M[B]
|
||||
}
|
||||
|
||||
trait Monad[M[_]] extends Applicative[M] {
|
||||
def flatten[T](m: M[M[T]]): M[T]
|
||||
}
|
||||
|
||||
implicit val optionMonad: Monad[Option] = new Monad[Option] {
|
||||
def apply[S, T](f: Option[S => T], v: Option[S]) = (f, v) match {
|
||||
case (Some(fv), Some(vv)) => Some(fv(vv))
|
||||
case _ => None
|
||||
}
|
||||
|
||||
def pure[S](s: => S) = Some(s)
|
||||
def map[S, T](f: S => T, v: Option[S]) = v map f
|
||||
def flatten[T](m: Option[Option[T]]): Option[T] = m.flatten
|
||||
}
|
||||
|
||||
implicit val listMonad: Monad[List] = new Monad[List] {
|
||||
def apply[S, T](f: List[S => T], v: List[S]) = for (fv <- f; vv <- v) yield fv(vv)
|
||||
def pure[S](s: => S) = s :: Nil
|
||||
def map[S, T](f: S => T, v: List[S]) = v map f
|
||||
def flatten[T](m: List[List[T]]): List[T] = m.flatten
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
trait Dag[Node <: Dag[Node]] { self: Node =>
|
||||
|
||||
def dependencies: Iterable[Node]
|
||||
def topologicalSort = Dag.topologicalSort(self)(_.dependencies)
|
||||
}
|
||||
object Dag {
|
||||
import scala.collection.{ mutable, JavaConverters }
|
||||
import JavaConverters.asScalaSetConverter
|
||||
|
||||
def topologicalSort[T](root: T)(dependencies: T => Iterable[T]): List[T] =
|
||||
topologicalSort(root :: Nil)(dependencies)
|
||||
|
||||
def topologicalSort[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): List[T] = {
|
||||
val discovered = new mutable.HashSet[T]
|
||||
val finished = (new java.util.LinkedHashSet[T]).asScala
|
||||
|
||||
def visitAll(nodes: Iterable[T]) = nodes foreach visit
|
||||
def visit(node: T): Unit = {
|
||||
if (!discovered(node)) {
|
||||
discovered(node) = true;
|
||||
try {
|
||||
visitAll(dependencies(node));
|
||||
} catch { case c: Cyclic => throw node :: c }
|
||||
finished += node
|
||||
()
|
||||
} else if (!finished(node))
|
||||
throw new Cyclic(node)
|
||||
}
|
||||
|
||||
visitAll(nodes)
|
||||
|
||||
finished.toList
|
||||
}
|
||||
|
||||
// doesn't check for cycles
|
||||
def topologicalSortUnchecked[T](node: T)(dependencies: T => Iterable[T]): List[T] =
|
||||
topologicalSortUnchecked(node :: Nil)(dependencies)
|
||||
|
||||
def topologicalSortUnchecked[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): List[T] = {
|
||||
val discovered = new mutable.HashSet[T]
|
||||
var finished: List[T] = Nil
|
||||
|
||||
def visitAll(nodes: Iterable[T]) = nodes foreach visit
|
||||
def visit(node: T): Unit = {
|
||||
if (!discovered(node)) {
|
||||
discovered(node) = true
|
||||
visitAll(dependencies(node))
|
||||
finished ::= node
|
||||
}
|
||||
}
|
||||
|
||||
visitAll(nodes);
|
||||
finished;
|
||||
}
|
||||
|
||||
final class Cyclic(val value: Any, val all: List[Any], val complete: Boolean)
|
||||
extends Exception(
|
||||
"Cyclic reference involving " +
|
||||
(if (complete) all.mkString("\n ", "\n ", "") else value)
|
||||
) {
|
||||
def this(value: Any) = this(value, value :: Nil, false)
|
||||
override def toString = getMessage
|
||||
|
||||
def ::(a: Any): Cyclic =
|
||||
if (complete)
|
||||
this
|
||||
else if (a == value)
|
||||
new Cyclic(value, all, true)
|
||||
else
|
||||
new Cyclic(value, a :: all, false)
|
||||
}
|
||||
|
||||
/** A directed graph with edges labeled positive or negative. */
|
||||
private[sbt] trait DirectedSignedGraph[Node] {
|
||||
|
||||
/**
|
||||
* Directed edge type that tracks the sign and target (head) vertex.
|
||||
* The sign can be obtained via [[isNegative]] and the target vertex via [[head]].
|
||||
*/
|
||||
type Arrow
|
||||
|
||||
/** List of initial nodes. */
|
||||
def nodes: List[Arrow]
|
||||
|
||||
/** Outgoing edges for `n`. */
|
||||
def dependencies(n: Node): List[Arrow]
|
||||
|
||||
/** `true` if the edge `a` is "negative", false if it is "positive". */
|
||||
def isNegative(a: Arrow): Boolean
|
||||
|
||||
/** The target of the directed edge `a`. */
|
||||
def head(a: Arrow): Node
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a directed graph defined by `graph` looking for a cycle that includes a "negative" edge.
|
||||
* The directed edges are weighted by the caller as "positive" or "negative".
|
||||
* If a cycle containing a "negative" edge is detected, its member edges are returned in order.
|
||||
* Otherwise, the empty list is returned.
|
||||
*/
|
||||
private[sbt] def findNegativeCycle[Node](graph: DirectedSignedGraph[Node]): List[graph.Arrow] = {
|
||||
import graph._
|
||||
val finished = new mutable.HashSet[Node]
|
||||
val visited = new mutable.HashSet[Node]
|
||||
|
||||
def visit(edges: List[Arrow], stack: List[Arrow]): List[Arrow] = edges match {
|
||||
case Nil => Nil
|
||||
case edge :: tail =>
|
||||
val node = head(edge)
|
||||
if (!visited(node)) {
|
||||
visited += node
|
||||
visit(dependencies(node), edge :: stack) match {
|
||||
case Nil =>
|
||||
finished += node
|
||||
visit(tail, stack)
|
||||
case cycle => cycle
|
||||
}
|
||||
} else if (!finished(node)) {
|
||||
// cycle. If a negative edge is involved, it is an error.
|
||||
val between = edge :: stack.takeWhile(f => head(f) != node)
|
||||
if (between exists isNegative)
|
||||
between
|
||||
else
|
||||
visit(tail, stack)
|
||||
} else
|
||||
visit(tail, stack)
|
||||
}
|
||||
|
||||
visit(graph.nodes, Nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import Types._
|
||||
|
||||
/**
|
||||
* A minimal heterogeneous list type. For background, see
|
||||
* https://apocalisp.wordpress.com/2010/07/06/type-level-programming-in-scala-part-6a-heterogeneous-list basics/
|
||||
*/
|
||||
sealed trait HList {
|
||||
type Wrap[M[_]] <: HList
|
||||
}
|
||||
|
||||
sealed trait HNil extends HList {
|
||||
type Wrap[M[_]] = HNil
|
||||
def :+:[G](g: G): G :+: HNil = HCons(g, this)
|
||||
|
||||
override def toString = "HNil"
|
||||
}
|
||||
|
||||
object HNil extends HNil
|
||||
|
||||
final case class HCons[H, T <: HList](head: H, tail: T) extends HList {
|
||||
type Wrap[M[_]] = M[H] :+: T#Wrap[M]
|
||||
def :+:[G](g: G): G :+: H :+: T = HCons(g, this)
|
||||
|
||||
override def toString = head + " :+: " + tail.toString
|
||||
}
|
||||
|
||||
object HList {
|
||||
// contains no type information: not even A
|
||||
implicit def fromList[A](list: Traversable[A]): HList =
|
||||
list.foldLeft(HNil: HList)((hl, v) => HCons(v, hl))
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package internal
|
||||
package util
|
||||
|
||||
import sjsonnew._
|
||||
import Types.:+:
|
||||
|
||||
trait HListFormats {
|
||||
implicit val lnilFormat1: JsonFormat[HNil] = forHNil(HNil: HNil)
|
||||
implicit val lnilFormat2: JsonFormat[HNil.type] = forHNil(HNil)
|
||||
|
||||
private def forHNil[A <: HNil](hnil: A): JsonFormat[A] = new JsonFormat[A] {
|
||||
def write[J](x: A, builder: Builder[J]): Unit = {
|
||||
builder.beginArray()
|
||||
builder.endArray()
|
||||
}
|
||||
|
||||
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): A = jsOpt match {
|
||||
case None => hnil
|
||||
case Some(js) => unbuilder.beginArray(js); unbuilder.endArray(); hnil
|
||||
}
|
||||
}
|
||||
|
||||
implicit def hconsFormat[H, T <: HList](
|
||||
implicit hf: JsonFormat[H],
|
||||
tf: HListJF[T]
|
||||
): JsonFormat[H :+: T] =
|
||||
new JsonFormat[H :+: T] {
|
||||
def write[J](hcons: H :+: T, builder: Builder[J]) = {
|
||||
builder.beginArray()
|
||||
hf.write(hcons.head, builder)
|
||||
tf.write(hcons.tail, builder)
|
||||
builder.endArray()
|
||||
}
|
||||
|
||||
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]) = jsOpt match {
|
||||
case None => HCons(hf.read(None, unbuilder), tf.read(None, unbuilder))
|
||||
case Some(js) =>
|
||||
unbuilder.beginArray(js)
|
||||
val hcons =
|
||||
HCons(hf.read(Some(unbuilder.nextElement), unbuilder), tf.read(Some(js), unbuilder))
|
||||
unbuilder.endArray()
|
||||
hcons
|
||||
}
|
||||
}
|
||||
|
||||
trait HListJF[A <: HList] {
|
||||
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): A
|
||||
def write[J](obj: A, builder: Builder[J]): Unit
|
||||
}
|
||||
|
||||
implicit def hconsHListJF[H, T <: HList](
|
||||
implicit hf: JsonFormat[H],
|
||||
tf: HListJF[T]
|
||||
): HListJF[H :+: T] =
|
||||
new HListJF[H :+: T] {
|
||||
def write[J](hcons: H :+: T, builder: Builder[J]) = {
|
||||
hf.write(hcons.head, builder)
|
||||
tf.write(hcons.tail, builder)
|
||||
}
|
||||
|
||||
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]) = jsOpt match {
|
||||
case None => HCons(hf.read(None, unbuilder), tf.read(None, unbuilder))
|
||||
case Some(js) =>
|
||||
HCons(hf.read(Some(unbuilder.nextElement), unbuilder), tf.read(Some(js), unbuilder))
|
||||
}
|
||||
}
|
||||
|
||||
implicit val lnilHListJF1: HListJF[HNil] = hnilHListJF(HNil: HNil)
|
||||
implicit val lnilHListJF2: HListJF[HNil.type] = hnilHListJF(HNil)
|
||||
|
||||
implicit def hnilHListJF[A <: HNil](hnil: A): HListJF[A] = new HListJF[A] {
|
||||
def write[J](hcons: A, builder: Builder[J]) = ()
|
||||
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]) = hnil
|
||||
}
|
||||
}
|
||||
|
||||
object HListFormats extends HListFormats
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
/** A mutable set interface that uses object identity to test for set membership.*/
|
||||
trait IDSet[T] {
|
||||
def apply(t: T): Boolean
|
||||
def contains(t: T): Boolean
|
||||
def +=(t: T): Unit
|
||||
def ++=(t: Iterable[T]): Unit
|
||||
def -=(t: T): Boolean
|
||||
def all: collection.Iterable[T]
|
||||
def toList: List[T]
|
||||
def isEmpty: Boolean
|
||||
def foreach(f: T => Unit): Unit
|
||||
def process[S](t: T)(ifSeen: S)(ifNew: => S): S
|
||||
}
|
||||
|
||||
object IDSet {
|
||||
implicit def toTraversable[T]: IDSet[T] => Traversable[T] = _.all
|
||||
def apply[T](values: T*): IDSet[T] = fromIterable(values)
|
||||
|
||||
def apply[T](values: Iterable[T]): IDSet[T] = fromIterable(values)
|
||||
|
||||
private def fromIterable[T](values: Iterable[T]): IDSet[T] = {
|
||||
val s = create[T]
|
||||
s ++= values
|
||||
s
|
||||
}
|
||||
|
||||
def create[T]: IDSet[T] = new IDSet[T] {
|
||||
private[this] val backing = new java.util.IdentityHashMap[T, AnyRef]
|
||||
private[this] val Dummy: AnyRef = ""
|
||||
|
||||
def apply(t: T) = contains(t)
|
||||
def contains(t: T) = backing.containsKey(t)
|
||||
def foreach(f: T => Unit) = all foreach f
|
||||
def +=(t: T) = { backing.put(t, Dummy); () }
|
||||
def ++=(t: Iterable[T]) = t foreach +=
|
||||
def -=(t: T) = if (backing.remove(t) eq null) false else true
|
||||
def all = backing.keySet.asScala
|
||||
def toList = all.toList
|
||||
def isEmpty = backing.isEmpty
|
||||
|
||||
def process[S](t: T)(ifSeen: S)(ifNew: => S) =
|
||||
if (contains(t)) ifSeen
|
||||
else {
|
||||
this += t; ifNew
|
||||
}
|
||||
|
||||
override def toString = backing.toString
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.lang.Runnable
|
||||
import java.util.concurrent.{ atomic, Executor, LinkedBlockingQueue }
|
||||
import atomic.{ AtomicBoolean, AtomicInteger }
|
||||
import Types.{ ConstK, Id }
|
||||
|
||||
object EvaluationState extends Enumeration {
|
||||
val New, Blocked, Ready, Calling, Evaluated = Value
|
||||
}
|
||||
|
||||
abstract class EvaluateSettings[ScopeType] {
|
||||
protected val init: Init[ScopeType]
|
||||
import init._
|
||||
|
||||
protected def executor: Executor
|
||||
protected def compiledSettings: Seq[Compiled[_]]
|
||||
|
||||
import EvaluationState.{ Value => EvaluationState, _ }
|
||||
|
||||
private[this] val complete = new LinkedBlockingQueue[Option[Throwable]]
|
||||
private[this] val static = PMap.empty[ScopedKey, INode]
|
||||
private[this] val allScopes: Set[ScopeType] = compiledSettings.map(_.key.scope).toSet
|
||||
|
||||
private[this] def getStatic[T](key: ScopedKey[T]): INode[T] =
|
||||
static get key getOrElse sys.error("Illegal reference to key " + key)
|
||||
|
||||
private[this] val transform: Initialize ~> INode = λ[Initialize ~> INode] {
|
||||
case k: Keyed[s, A1$] @unchecked => single(getStatic(k.scopedKey), k.transform)
|
||||
case a: Apply[k, A1$] @unchecked =>
|
||||
new MixedNode[k, A1$](
|
||||
a.alist.transform[Initialize, INode](a.inputs, transform),
|
||||
a.f,
|
||||
a.alist
|
||||
)
|
||||
case b: Bind[s, A1$] @unchecked => new BindNode[s, A1$](transform(b.in), x => transform(b.f(x)))
|
||||
case v: Value[A1$] @unchecked => constant(v.value)
|
||||
case v: ValidationCapture[A1$] @unchecked => strictConstant(v.key: A1$)
|
||||
case t: TransformCapture => strictConstant(t.f: A1$)
|
||||
case o: Optional[s, A1$] @unchecked =>
|
||||
o.a match {
|
||||
case None => constant(() => o.f(None))
|
||||
case Some(i) => single[s, A1$](transform(i), x => o.f(Some(x)))
|
||||
}
|
||||
case x if x == StaticScopes =>
|
||||
strictConstant(allScopes.asInstanceOf[A1$]) // can't convince scalac that StaticScopes => T == Set[Scope]
|
||||
}
|
||||
|
||||
private[this] lazy val roots: Seq[INode[_]] = compiledSettings flatMap { cs =>
|
||||
(cs.settings map { s =>
|
||||
val t = transform(s.init)
|
||||
static(s.key) = t
|
||||
t
|
||||
}): Seq[INode[_]]
|
||||
}
|
||||
|
||||
private[this] val running = new AtomicInteger
|
||||
private[this] val cancel = new AtomicBoolean(false)
|
||||
|
||||
def run(implicit delegates: ScopeType => Seq[ScopeType]): Settings[ScopeType] = {
|
||||
assert(running.get() == 0, "Already running")
|
||||
startWork()
|
||||
roots.foreach(_.registerIfNew())
|
||||
workComplete()
|
||||
complete.take() foreach { ex =>
|
||||
cancel.set(true)
|
||||
throw ex
|
||||
}
|
||||
getResults(delegates)
|
||||
}
|
||||
|
||||
private[this] def getResults(implicit delegates: ScopeType => Seq[ScopeType]) =
|
||||
static.toTypedSeq.foldLeft(empty) {
|
||||
case (ss, static.TPair(key, node)) =>
|
||||
if (key.key.isLocal) ss else ss.set(key.scope, key.key, node.get)
|
||||
}
|
||||
|
||||
private[this] val getValue = λ[INode ~> Id](_.get)
|
||||
|
||||
private[this] def submitEvaluate(node: INode[_]) = submit(node.evaluate())
|
||||
|
||||
private[this] def submitCallComplete[T](node: BindNode[_, T], value: T) =
|
||||
submit(node.callComplete(value))
|
||||
|
||||
private[this] def submit(work: => Unit): Unit = {
|
||||
startWork()
|
||||
executor.execute(new Runnable { def run = if (!cancel.get()) run0(work) })
|
||||
}
|
||||
|
||||
private[this] def run0(work: => Unit): Unit = {
|
||||
try {
|
||||
work
|
||||
} catch { case e: Throwable => complete.put(Some(e)) }
|
||||
workComplete()
|
||||
}
|
||||
|
||||
private[this] def startWork(): Unit = { running.incrementAndGet(); () }
|
||||
|
||||
private[this] def workComplete(): Unit =
|
||||
if (running.decrementAndGet() == 0)
|
||||
complete.put(None)
|
||||
|
||||
private[this] sealed abstract class INode[T] {
|
||||
private[this] var state: EvaluationState = New
|
||||
private[this] var value: T = _
|
||||
private[this] val blocking = new collection.mutable.ListBuffer[INode[_]]
|
||||
private[this] var blockedOn: Int = 0
|
||||
private[this] val calledBy = new collection.mutable.ListBuffer[BindNode[_, T]]
|
||||
|
||||
override def toString =
|
||||
getClass.getName + " (state=" + state + ",blockedOn=" + blockedOn + ",calledBy=" + calledBy.size + ",blocking=" + blocking.size + "): " +
|
||||
keyString
|
||||
|
||||
private[this] def keyString =
|
||||
(static.toSeq.flatMap {
|
||||
case (key, value) =>
|
||||
if (value eq this) init.showFullKey.show(key) :: Nil else List.empty[String]
|
||||
}).headOption getOrElse "non-static"
|
||||
|
||||
final def get: T = synchronized {
|
||||
assert(value != null, toString + " not evaluated")
|
||||
value
|
||||
}
|
||||
|
||||
final def doneOrBlock(from: INode[_]): Boolean = synchronized {
|
||||
val ready = state == Evaluated
|
||||
if (!ready) {
|
||||
blocking += from
|
||||
()
|
||||
}
|
||||
registerIfNew()
|
||||
ready
|
||||
}
|
||||
|
||||
final def isDone: Boolean = synchronized { state == Evaluated }
|
||||
final def isNew: Boolean = synchronized { state == New }
|
||||
final def isCalling: Boolean = synchronized { state == Calling }
|
||||
final def registerIfNew(): Unit = synchronized { if (state == New) register() }
|
||||
|
||||
private[this] def register(): Unit = {
|
||||
assert(state == New, "Already registered and: " + toString)
|
||||
val deps = dependsOn
|
||||
blockedOn = deps.size - deps.count(_.doneOrBlock(this))
|
||||
if (blockedOn == 0)
|
||||
schedule()
|
||||
else
|
||||
state = Blocked
|
||||
}
|
||||
|
||||
final def schedule(): Unit = synchronized {
|
||||
assert(state == New || state == Blocked, "Invalid state for schedule() call: " + toString)
|
||||
state = Ready
|
||||
submitEvaluate(this)
|
||||
}
|
||||
|
||||
final def unblocked(): Unit = synchronized {
|
||||
assert(state == Blocked, "Invalid state for unblocked() call: " + toString)
|
||||
blockedOn -= 1
|
||||
assert(blockedOn >= 0, "Negative blockedOn: " + blockedOn + " for " + toString)
|
||||
if (blockedOn == 0) schedule()
|
||||
}
|
||||
|
||||
final def evaluate(): Unit = synchronized { evaluate0() }
|
||||
|
||||
protected final def makeCall(source: BindNode[_, T], target: INode[T]): Unit = {
|
||||
assert(state == Ready, "Invalid state for call to makeCall: " + toString)
|
||||
state = Calling
|
||||
target.call(source)
|
||||
}
|
||||
|
||||
protected final def setValue(v: T): Unit = {
|
||||
assert(
|
||||
state != Evaluated,
|
||||
"Already evaluated (trying to set value to " + v + "): " + toString
|
||||
)
|
||||
if (v == null) sys.error("Setting value cannot be null: " + keyString)
|
||||
value = v
|
||||
state = Evaluated
|
||||
blocking foreach { _.unblocked() }
|
||||
blocking.clear()
|
||||
calledBy foreach { node =>
|
||||
submitCallComplete(node, value)
|
||||
}
|
||||
calledBy.clear()
|
||||
}
|
||||
|
||||
final def call(by: BindNode[_, T]): Unit = synchronized {
|
||||
registerIfNew()
|
||||
state match {
|
||||
case Evaluated => submitCallComplete(by, value)
|
||||
case _ =>
|
||||
calledBy += by
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
protected def dependsOn: Seq[INode[_]]
|
||||
protected def evaluate0(): Unit
|
||||
}
|
||||
|
||||
private[this] def strictConstant[T](v: T): INode[T] = constant(() => v)
|
||||
|
||||
private[this] def constant[T](f: () => T): INode[T] =
|
||||
new MixedNode[ConstK[Unit]#l, T]((), _ => f(), AList.empty)
|
||||
|
||||
private[this] def single[S, T](in: INode[S], f: S => T): INode[T] =
|
||||
new MixedNode[λ[L[x] => L[S]], T](in, f, AList.single[S])
|
||||
|
||||
private[this] final class BindNode[S, T](in: INode[S], f: S => INode[T]) extends INode[T] {
|
||||
protected def dependsOn = in :: Nil
|
||||
protected def evaluate0(): Unit = makeCall(this, f(in.get))
|
||||
def callComplete(value: T): Unit = synchronized {
|
||||
assert(isCalling, "Invalid state for callComplete(" + value + "): " + toString)
|
||||
setValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
private[this] final class MixedNode[K[L[x]], T](in: K[INode], f: K[Id] => T, alist: AList[K])
|
||||
extends INode[T] {
|
||||
protected def dependsOn = alist.toList(in)
|
||||
protected def evaluate0(): Unit = setValue(f(alist.transform(in, getValue)))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import Types._
|
||||
import Classes.Applicative
|
||||
|
||||
/** A higher-kinded heterogeneous list of elements that share the same type constructor `M[_]`. */
|
||||
sealed trait KList[+M[_]] {
|
||||
type Transform[N[_]] <: KList[N]
|
||||
|
||||
/** Apply the natural transformation `f` to each element. */
|
||||
def transform[N[_]](f: M ~> N): Transform[N]
|
||||
|
||||
/** Folds this list using a function that operates on the homogeneous type of the elements of this list. */
|
||||
def foldr[B](f: (M[_], B) => B, init: B): B
|
||||
|
||||
/** Applies `f` to the elements of this list in the applicative functor defined by `ap`. */
|
||||
def apply[N[x] >: M[x], Z](f: Transform[Id] => Z)(implicit ap: Applicative[N]): N[Z]
|
||||
|
||||
/** Equivalent to `transform(f) . apply(x => x)`, this is the essence of the iterator at the level of natural transformations.*/
|
||||
def traverse[N[_], P[_]](f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[Transform[P]]
|
||||
|
||||
/** Discards the heterogeneous type information and constructs a plain List from this KList's elements. */
|
||||
def toList: List[M[_]]
|
||||
}
|
||||
object KList {
|
||||
type Aux[+M[_], Transform0[N[_]]] = KList[M] { type Transform[N[_]] = Transform0[N] }
|
||||
}
|
||||
|
||||
final case class KCons[H, +T <: KList[M], +M[_]](head: M[H], tail: T) extends KList[M] {
|
||||
final type Transform[N[_]] = KCons[H, tail.Transform[N], N]
|
||||
|
||||
def transform[N[_]](f: M ~> N) = KCons(f(head), tail.transform(f))
|
||||
def toList: List[M[_]] = head :: tail.toList
|
||||
|
||||
def apply[N[x] >: M[x], Z](f: Transform[Id] => Z)(implicit ap: Applicative[N]): N[Z] = {
|
||||
val g = (t: tail.Transform[Id]) => (h: H) => f(KCons[H, tail.Transform[Id], Id](h, t))
|
||||
ap.apply(tail.apply[N, H => Z](g), head)
|
||||
}
|
||||
|
||||
def traverse[N[_], P[_]](f: M ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[Transform[P]] = {
|
||||
val tt: N[tail.Transform[P]] = tail.traverse[N, P](f)
|
||||
val g = (t: tail.Transform[P]) => (h: P[H]) => KCons(h, t)
|
||||
np.apply(np.map(g, tt), f(head))
|
||||
}
|
||||
|
||||
def :^:[A, N[x] >: M[x]](h: N[A]) = KCons(h, this)
|
||||
override def foldr[B](f: (M[_], B) => B, init: B): B = f(head, tail.foldr(f, init))
|
||||
}
|
||||
|
||||
sealed abstract class KNil extends KList[NothingK] {
|
||||
final type Transform[N[_]] = KNil
|
||||
final def transform[N[_]](f: NothingK ~> N): Transform[N] = KNil
|
||||
final def foldr[B](f: (NothingK[_], B) => B, init: B): B = init
|
||||
final def toList = Nil
|
||||
final def apply[N[x], Z](f: KNil => Z)(implicit ap: Applicative[N]): N[Z] = ap.pure(f(KNil))
|
||||
|
||||
final def traverse[N[_], P[_]](f: NothingK ~> (N ∙ P)#l)(implicit np: Applicative[N]): N[KNil] =
|
||||
np.pure(KNil: KNil)
|
||||
}
|
||||
|
||||
case object KNil extends KNil {
|
||||
def :^:[M[_], H](h: M[H]): KCons[H, KNil, M] = KCons(h, this)
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import collection.mutable
|
||||
|
||||
trait RMap[K[_], V[_]] {
|
||||
def apply[T](k: K[T]): V[T]
|
||||
def get[T](k: K[T]): Option[V[T]]
|
||||
def contains[T](k: K[T]): Boolean
|
||||
def toSeq: Seq[(K[_], V[_])]
|
||||
|
||||
def toTypedSeq: Seq[TPair[_]] = toSeq.map {
|
||||
case (k: K[t], v) => TPair[t](k, v.asInstanceOf[V[t]])
|
||||
}
|
||||
|
||||
def keys: Iterable[K[_]]
|
||||
def values: Iterable[V[_]]
|
||||
def isEmpty: Boolean
|
||||
|
||||
sealed case class TPair[T](key: K[T], value: V[T])
|
||||
}
|
||||
|
||||
trait IMap[K[_], V[_]] extends (K ~> V) with RMap[K, V] {
|
||||
def put[T](k: K[T], v: V[T]): IMap[K, V]
|
||||
def remove[T](k: K[T]): IMap[K, V]
|
||||
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): IMap[K, V]
|
||||
def mapValues[V2[_]](f: V ~> V2): IMap[K, V2]
|
||||
def mapSeparate[VL[_], VR[_]](f: V ~> λ[T => Either[VL[T], VR[T]]]): (IMap[K, VL], IMap[K, VR])
|
||||
}
|
||||
|
||||
trait PMap[K[_], V[_]] extends (K ~> V) with RMap[K, V] {
|
||||
def update[T](k: K[T], v: V[T]): Unit
|
||||
def remove[T](k: K[T]): Option[V[T]]
|
||||
def getOrUpdate[T](k: K[T], make: => V[T]): V[T]
|
||||
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): V[T]
|
||||
}
|
||||
|
||||
object PMap {
|
||||
implicit def toFunction[K[_], V[_]](map: PMap[K, V]): K[_] => V[_] = k => map(k)
|
||||
def empty[K[_], V[_]]: PMap[K, V] = new DelegatingPMap[K, V](new mutable.HashMap)
|
||||
}
|
||||
|
||||
object IMap {
|
||||
|
||||
/**
|
||||
* Only suitable for K that is invariant in its type parameter.
|
||||
* Option and List keys are not suitable, for example,
|
||||
* because None <:< Option[String] and None <: Option[Int].
|
||||
*/
|
||||
def empty[K[_], V[_]]: IMap[K, V] = new IMap0[K, V](Map.empty)
|
||||
|
||||
private[sbt] def fromJMap[K[_], V[_]](map: java.util.Map[K[_], V[_]]): IMap[K, V] =
|
||||
new IMap0[K, V](new WrappedMap(map))
|
||||
|
||||
private[sbt] class IMap0[K[_], V[_]](val backing: Map[K[_], V[_]])
|
||||
extends AbstractRMap[K, V]
|
||||
with IMap[K, V] {
|
||||
def get[T](k: K[T]): Option[V[T]] = (backing get k).asInstanceOf[Option[V[T]]]
|
||||
def put[T](k: K[T], v: V[T]) = new IMap0[K, V](backing.updated(k, v))
|
||||
def remove[T](k: K[T]) = new IMap0[K, V](backing - k)
|
||||
|
||||
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]) =
|
||||
put(k, f(this get k getOrElse init))
|
||||
|
||||
def mapValues[V2[_]](f: V ~> V2) =
|
||||
new IMap0[K, V2](Map(backing.iterator.map { case (k, v) => k -> f(v) }.toArray: _*))
|
||||
|
||||
def mapSeparate[VL[_], VR[_]](f: V ~> λ[T => Either[VL[T], VR[T]]]) = {
|
||||
val left = new java.util.concurrent.ConcurrentHashMap[K[_], VL[_]]
|
||||
val right = new java.util.concurrent.ConcurrentHashMap[K[_], VR[_]]
|
||||
Par(backing.toVector).foreach {
|
||||
case (k, v) =>
|
||||
f(v) match {
|
||||
case Left(l) => left.put(k, l)
|
||||
case Right(r) => right.put(k, r)
|
||||
}
|
||||
}
|
||||
(new IMap0[K, VL](new WrappedMap(left)), new IMap0[K, VR](new WrappedMap(right)))
|
||||
}
|
||||
|
||||
def toSeq = backing.toSeq
|
||||
def keys = backing.keys
|
||||
def values = backing.values
|
||||
def isEmpty = backing.isEmpty
|
||||
|
||||
override def toString = backing.toString
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractRMap[K[_], V[_]] extends RMap[K, V] {
|
||||
def apply[T](k: K[T]): V[T] = get(k).get
|
||||
def contains[T](k: K[T]): Boolean = get(k).isDefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Only suitable for K that is invariant in its type parameter.
|
||||
* Option and List keys are not suitable, for example,
|
||||
* because None <:< Option[String] and None <: Option[Int].
|
||||
*/
|
||||
class DelegatingPMap[K[_], V[_]](backing: mutable.Map[K[_], V[_]])
|
||||
extends AbstractRMap[K, V]
|
||||
with PMap[K, V] {
|
||||
def get[T](k: K[T]): Option[V[T]] = cast[T](backing.get(k))
|
||||
def update[T](k: K[T], v: V[T]): Unit = { backing(k) = v }
|
||||
def remove[T](k: K[T]) = cast(backing.remove(k))
|
||||
def getOrUpdate[T](k: K[T], make: => V[T]) = cast[T](backing.getOrElseUpdate(k, make))
|
||||
|
||||
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): V[T] = {
|
||||
val v = f(this get k getOrElse init)
|
||||
update(k, v)
|
||||
v
|
||||
}
|
||||
|
||||
def toSeq = backing.toSeq
|
||||
def keys = backing.keys
|
||||
def values = backing.values
|
||||
def isEmpty = backing.isEmpty
|
||||
|
||||
private[this] def cast[T](v: V[_]): V[T] = v.asInstanceOf[V[T]]
|
||||
private[this] def cast[T](o: Option[V[_]]): Option[V[T]] = o map cast[T]
|
||||
|
||||
override def toString = backing.toString
|
||||
}
|
||||
|
|
@ -0,0 +1,966 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import Types._
|
||||
import sbt.util.Show
|
||||
import Util.{ nil, nilSeq }
|
||||
|
||||
sealed trait Settings[ScopeType] {
|
||||
def data: Map[ScopeType, AttributeMap]
|
||||
def keys(scope: ScopeType): Set[AttributeKey[_]]
|
||||
def scopes: Set[ScopeType]
|
||||
def definingScope(scope: ScopeType, key: AttributeKey[_]): Option[ScopeType]
|
||||
def allKeys[T](f: (ScopeType, AttributeKey[_]) => T): Seq[T]
|
||||
def get[T](scope: ScopeType, key: AttributeKey[T]): Option[T]
|
||||
def getDirect[T](scope: ScopeType, key: AttributeKey[T]): Option[T]
|
||||
def set[T](scope: ScopeType, key: AttributeKey[T], value: T): Settings[ScopeType]
|
||||
}
|
||||
|
||||
private final class Settings0[ScopeType](
|
||||
val data: Map[ScopeType, AttributeMap],
|
||||
val delegates: ScopeType => Seq[ScopeType]
|
||||
) extends Settings[ScopeType] {
|
||||
|
||||
def scopes: Set[ScopeType] = data.keySet
|
||||
def keys(scope: ScopeType) = data(scope).keys.toSet
|
||||
|
||||
def allKeys[T](f: (ScopeType, AttributeKey[_]) => T): Seq[T] =
|
||||
data.flatMap { case (scope, map) => map.keys.map(k => f(scope, k)) }.toSeq
|
||||
|
||||
def get[T](scope: ScopeType, key: AttributeKey[T]): Option[T] =
|
||||
delegates(scope).flatMap(sc => getDirect(sc, key)).headOption
|
||||
|
||||
def definingScope(scope: ScopeType, key: AttributeKey[_]): Option[ScopeType] =
|
||||
delegates(scope).find(sc => getDirect(sc, key).isDefined)
|
||||
|
||||
def getDirect[T](scope: ScopeType, key: AttributeKey[T]): Option[T] =
|
||||
(data get scope).flatMap(_ get key)
|
||||
|
||||
def set[T](scope: ScopeType, key: AttributeKey[T], value: T): Settings[ScopeType] = {
|
||||
val map = data getOrElse (scope, AttributeMap.empty)
|
||||
val newData = data.updated(scope, map.put(key, value))
|
||||
new Settings0(newData, delegates)
|
||||
}
|
||||
}
|
||||
|
||||
// delegates should contain the input Scope as the first entry
|
||||
// this trait is intended to be mixed into an object
|
||||
trait Init[ScopeType] {
|
||||
|
||||
/** The Show instance used when a detailed String needs to be generated.
|
||||
* It is typically used when no context is available.
|
||||
*/
|
||||
def showFullKey: Show[ScopedKey[_]]
|
||||
|
||||
sealed case class ScopedKey[T](scope: ScopeType, key: AttributeKey[T])
|
||||
extends KeyedInitialize[T] {
|
||||
def scopedKey = this
|
||||
}
|
||||
|
||||
type SettingSeq[T] = Seq[Setting[T]]
|
||||
type ScopedMap = IMap[ScopedKey, SettingSeq]
|
||||
type CompiledMap = Map[ScopedKey[_], Compiled[_]]
|
||||
type MapScoped = ScopedKey ~> ScopedKey
|
||||
type ValidatedRef[T] = Either[Undefined, ScopedKey[T]]
|
||||
type ValidatedInit[T] = Either[Seq[Undefined], Initialize[T]]
|
||||
type ValidateRef = ScopedKey ~> ValidatedRef
|
||||
type ScopeLocal = ScopedKey[_] => Seq[Setting[_]]
|
||||
type MapConstant = ScopedKey ~> Option
|
||||
|
||||
private[sbt] abstract class ValidateKeyRef {
|
||||
def apply[T](key: ScopedKey[T], selfRefOk: Boolean): ValidatedRef[T]
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of this initialization is the composition of applied transformations.
|
||||
* This can be useful when dealing with dynamic Initialize values.
|
||||
*/
|
||||
lazy val capturedTransformations: Initialize[Initialize ~> Initialize] =
|
||||
new TransformCapture(idK[Initialize])
|
||||
|
||||
def setting[T](
|
||||
key: ScopedKey[T],
|
||||
init: Initialize[T],
|
||||
pos: SourcePosition = NoPosition
|
||||
): Setting[T] = new Setting[T](key, init, pos)
|
||||
|
||||
def valueStrict[T](value: T): Initialize[T] = pure(() => value)
|
||||
def value[T](value: => T): Initialize[T] = pure(value _)
|
||||
def pure[T](value: () => T): Initialize[T] = new Value(value)
|
||||
def optional[T, U](i: Initialize[T])(f: Option[T] => U): Initialize[U] = new Optional(Some(i), f)
|
||||
|
||||
def update[T](key: ScopedKey[T])(f: T => T): Setting[T] =
|
||||
setting[T](key, map(key)(f), NoPosition)
|
||||
|
||||
def bind[S, T](in: Initialize[S])(f: S => Initialize[T]): Initialize[T] = new Bind(f, in)
|
||||
|
||||
def map[S, T](in: Initialize[S])(f: S => T): Initialize[T] =
|
||||
new Apply[λ[L[x] => L[S]], T](f, in, AList.single[S])
|
||||
|
||||
def app[K[L[x]], T](inputs: K[Initialize])(f: K[Id] => T)(
|
||||
implicit alist: AList[K]
|
||||
): Initialize[T] = new Apply[K, T](f, inputs, alist)
|
||||
|
||||
def uniform[S, T](inputs: Seq[Initialize[S]])(f: Seq[S] => T): Initialize[T] =
|
||||
new Apply[λ[L[x] => List[L[S]]], T](f, inputs.toList, AList.seq[S])
|
||||
|
||||
/**
|
||||
* The result of this initialization is the validated `key`.
|
||||
* No dependency is introduced on `key`. If `selfRefOk` is true, validation will not fail if the key is referenced by a definition of `key`.
|
||||
* That is, key := f(validated(key).value) is allowed only if `selfRefOk == true`.
|
||||
*/
|
||||
private[sbt] final def validated[T](
|
||||
key: ScopedKey[T],
|
||||
selfRefOk: Boolean
|
||||
): ValidationCapture[T] =
|
||||
new ValidationCapture(key, selfRefOk)
|
||||
|
||||
/**
|
||||
* Constructs a derived setting that will be automatically defined in every scope where one of its dependencies
|
||||
* is explicitly defined and the where the scope matches `filter`.
|
||||
* A setting initialized with dynamic dependencies is only allowed if `allowDynamic` is true.
|
||||
* Only the static dependencies are tracked, however. Dependencies on previous values do not introduce a derived setting either.
|
||||
*/
|
||||
final def derive[T](
|
||||
s: Setting[T],
|
||||
allowDynamic: Boolean = false,
|
||||
filter: ScopeType => Boolean = const(true),
|
||||
trigger: AttributeKey[_] => Boolean = const(true),
|
||||
default: Boolean = false
|
||||
): Setting[T] = {
|
||||
deriveAllowed(s, allowDynamic) foreach sys.error
|
||||
val d = new DerivedSetting[T](s.key, s.init, s.pos, filter, trigger)
|
||||
if (default) d.default() else d
|
||||
}
|
||||
|
||||
def deriveAllowed[T](s: Setting[T], allowDynamic: Boolean): Option[String] = s.init match {
|
||||
case _: Bind[_, _] if !allowDynamic => Some("Cannot derive from dynamic dependencies.")
|
||||
case _ => None
|
||||
}
|
||||
|
||||
// id is used for equality
|
||||
private[sbt] final def defaultSetting[T](s: Setting[T]): Setting[T] = s.default()
|
||||
|
||||
private[sbt] def defaultSettings(ss: Seq[Setting[_]]): Seq[Setting[_]] =
|
||||
ss.map(s => defaultSetting(s))
|
||||
|
||||
private[this] final val nextID = new java.util.concurrent.atomic.AtomicLong
|
||||
private[this] final def nextDefaultID(): Long = nextID.incrementAndGet()
|
||||
|
||||
def empty(implicit delegates: ScopeType => Seq[ScopeType]): Settings[ScopeType] =
|
||||
new Settings0(Map.empty, delegates)
|
||||
|
||||
def asTransform(s: Settings[ScopeType]): ScopedKey ~> Id = λ[ScopedKey ~> Id](k => getValue(s, k))
|
||||
|
||||
def getValue[T](s: Settings[ScopeType], k: ScopedKey[T]) =
|
||||
s.get(k.scope, k.key) getOrElse (throw new InvalidReference(k))
|
||||
|
||||
def asFunction[T](s: Settings[ScopeType]): ScopedKey[T] => T = k => getValue(s, k)
|
||||
|
||||
def mapScope(f: ScopeType => ScopeType): MapScoped = new MapScoped {
|
||||
def apply[T](k: ScopedKey[T]): ScopedKey[T] = k.copy(scope = f(k.scope))
|
||||
}
|
||||
|
||||
private final class InvalidReference(val key: ScopedKey[_])
|
||||
extends RuntimeException(
|
||||
"Internal settings error: invalid reference to " + showFullKey.show(key)
|
||||
)
|
||||
|
||||
private[this] def applyDefaults(ss: Seq[Setting[_]]): Seq[Setting[_]] = {
|
||||
val result = new java.util.LinkedHashSet[Setting[_]]
|
||||
val others = new java.util.ArrayList[Setting[_]]
|
||||
ss.foreach {
|
||||
case u: DefaultSetting[_] => result.add(u)
|
||||
case r => others.add(r)
|
||||
}
|
||||
result.addAll(others)
|
||||
import scala.collection.JavaConverters._
|
||||
result.asScala.toVector
|
||||
}
|
||||
|
||||
def compiled(init: Seq[Setting[_]], actual: Boolean = true)(
|
||||
implicit delegates: ScopeType => Seq[ScopeType],
|
||||
scopeLocal: ScopeLocal,
|
||||
display: Show[ScopedKey[_]]
|
||||
): CompiledMap = {
|
||||
val initDefaults = applyDefaults(init)
|
||||
// inject derived settings into scopes where their dependencies are directly defined
|
||||
// and prepend per-scope settings
|
||||
val derived = deriveAndLocal(initDefaults, mkDelegates(delegates))
|
||||
// group by Scope/Key, dropping dead initializations
|
||||
val sMap: ScopedMap = grouped(derived)
|
||||
// delegate references to undefined values according to 'delegates'
|
||||
val dMap: ScopedMap =
|
||||
if (actual) delegate(sMap)(delegates, display) else sMap
|
||||
// merge Seq[Setting[_]] into Compiled
|
||||
compile(dMap)
|
||||
}
|
||||
|
||||
@deprecated("Use makeWithCompiledMap", "1.4.0")
|
||||
def make(init: Seq[Setting[_]])(
|
||||
implicit delegates: ScopeType => Seq[ScopeType],
|
||||
scopeLocal: ScopeLocal,
|
||||
display: Show[ScopedKey[_]]
|
||||
): Settings[ScopeType] = makeWithCompiledMap(init)._2
|
||||
|
||||
def makeWithCompiledMap(init: Seq[Setting[_]])(
|
||||
implicit delegates: ScopeType => Seq[ScopeType],
|
||||
scopeLocal: ScopeLocal,
|
||||
display: Show[ScopedKey[_]]
|
||||
): (CompiledMap, Settings[ScopeType]) = {
|
||||
val cMap = compiled(init)(delegates, scopeLocal, display)
|
||||
// order the initializations. cyclic references are detected here.
|
||||
val ordered: Seq[Compiled[_]] = sort(cMap)
|
||||
// evaluation: apply the initializations.
|
||||
try {
|
||||
(cMap, applyInits(ordered))
|
||||
} catch {
|
||||
case rru: RuntimeUndefined =>
|
||||
throw Uninitialized(cMap.keys.toSeq, delegates, rru.undefined, true)
|
||||
}
|
||||
}
|
||||
|
||||
def sort(cMap: CompiledMap): Seq[Compiled[_]] =
|
||||
Dag.topologicalSort(cMap.values)(_.dependencies.map(cMap))
|
||||
|
||||
def compile(sMap: ScopedMap): CompiledMap = sMap match {
|
||||
case m: IMap.IMap0[ScopedKey, SettingSeq] @unchecked =>
|
||||
Par(m.backing.toVector)
|
||||
.map {
|
||||
case (k, ss) =>
|
||||
val deps = ss.flatMap(_.dependencies).toSet
|
||||
(
|
||||
k,
|
||||
new Compiled(k.asInstanceOf[ScopedKey[Any]], deps, ss.asInstanceOf[SettingSeq[Any]])
|
||||
)
|
||||
}
|
||||
.toVector
|
||||
.toMap
|
||||
case _ =>
|
||||
sMap.toTypedSeq.map {
|
||||
case sMap.TPair(k, ss) =>
|
||||
val deps = ss.flatMap(_.dependencies)
|
||||
(k, new Compiled(k, deps, ss))
|
||||
}.toMap
|
||||
}
|
||||
|
||||
def grouped(init: Seq[Setting[_]]): ScopedMap = {
|
||||
val result = new java.util.HashMap[ScopedKey[_], Seq[Setting[_]]]
|
||||
init.foreach { s =>
|
||||
result.putIfAbsent(s.key, Vector(s)) match {
|
||||
case null =>
|
||||
case ss => result.put(s.key, if (s.definitive) Vector(s) else ss :+ s)
|
||||
}
|
||||
}
|
||||
IMap.fromJMap[ScopedKey, SettingSeq](
|
||||
result.asInstanceOf[java.util.Map[ScopedKey[_], SettingSeq[_]]]
|
||||
)
|
||||
}
|
||||
|
||||
def add[T](m: ScopedMap, s: Setting[T]): ScopedMap =
|
||||
m.mapValue[T](s.key, Vector.empty[Setting[T]], ss => append(ss, s))
|
||||
|
||||
def append[T](ss: Seq[Setting[T]], s: Setting[T]): Seq[Setting[T]] =
|
||||
if (s.definitive) Vector(s) else ss :+ s
|
||||
|
||||
def addLocal(init: Seq[Setting[_]])(implicit scopeLocal: ScopeLocal): Seq[Setting[_]] =
|
||||
Par(init).map(_.dependencies flatMap scopeLocal).toVector.flatten ++ init
|
||||
|
||||
def delegate(sMap: ScopedMap)(
|
||||
implicit delegates: ScopeType => Seq[ScopeType],
|
||||
display: Show[ScopedKey[_]]
|
||||
): ScopedMap = {
|
||||
def refMap(ref: Setting[_], isFirst: Boolean) = new ValidateKeyRef {
|
||||
def apply[T](k: ScopedKey[T], selfRefOk: Boolean) =
|
||||
delegateForKey(sMap, k, delegates(k.scope), ref, selfRefOk || !isFirst)
|
||||
}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
val undefined = new java.util.ArrayList[Undefined]
|
||||
val result = new java.util.concurrent.ConcurrentHashMap[ScopedKey[_], Any]
|
||||
val backing = sMap.toSeq
|
||||
Par(backing).foreach {
|
||||
case (key, settings) =>
|
||||
val valid = new java.util.ArrayList[Setting[_]]
|
||||
val undefs = new java.util.ArrayList[Undefined]
|
||||
def validate(s: Setting[_], first: Boolean): Unit = {
|
||||
s.validateKeyReferenced(refMap(s, first)) match {
|
||||
case Right(v) => valid.add(v); ()
|
||||
case Left(us) => us.foreach(u => undefs.add(u))
|
||||
}
|
||||
}
|
||||
settings.headOption match {
|
||||
case Some(s) =>
|
||||
validate(s, true)
|
||||
settings.tail.foreach(validate(_, false))
|
||||
case _ =>
|
||||
}
|
||||
if (undefs.isEmpty) result.put(key, valid.asScala.toVector)
|
||||
else undefined.addAll(undefs)
|
||||
}
|
||||
|
||||
if (undefined.isEmpty)
|
||||
IMap.fromJMap[ScopedKey, SettingSeq](
|
||||
result.asInstanceOf[java.util.Map[ScopedKey[_], SettingSeq[_]]]
|
||||
)
|
||||
else
|
||||
throw Uninitialized(sMap.keys.toSeq, delegates, undefined.asScala.toList, false)
|
||||
}
|
||||
|
||||
private[this] def delegateForKey[T](
|
||||
sMap: ScopedMap,
|
||||
k: ScopedKey[T],
|
||||
scopes: Seq[ScopeType],
|
||||
ref: Setting[_],
|
||||
selfRefOk: Boolean
|
||||
): Either[Undefined, ScopedKey[T]] = {
|
||||
val skeys = scopes.iterator.map(x => ScopedKey(x, k.key))
|
||||
val definedAt = skeys.find(sk => (selfRefOk || ref.key != sk) && (sMap contains sk))
|
||||
definedAt.toRight(Undefined(ref, k))
|
||||
}
|
||||
|
||||
private[this] def applyInits(ordered: Seq[Compiled[_]])(
|
||||
implicit delegates: ScopeType => Seq[ScopeType]
|
||||
): Settings[ScopeType] = {
|
||||
val x =
|
||||
java.util.concurrent.Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors)
|
||||
try {
|
||||
val eval: EvaluateSettings[ScopeType] = new EvaluateSettings[ScopeType] {
|
||||
override val init: Init.this.type = Init.this
|
||||
def compiledSettings = ordered
|
||||
def executor = x
|
||||
}
|
||||
eval.run
|
||||
} finally {
|
||||
x.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
def showUndefined(
|
||||
u: Undefined,
|
||||
validKeys: Seq[ScopedKey[_]],
|
||||
delegates: ScopeType => Seq[ScopeType]
|
||||
)(
|
||||
implicit display: Show[ScopedKey[_]]
|
||||
): String = {
|
||||
val guessed = guessIntendedScope(validKeys, delegates, u.referencedKey)
|
||||
val derived = u.defining.isDerived
|
||||
val refString = display.show(u.defining.key)
|
||||
val sourceString = if (derived) "" else parenPosString(u.defining)
|
||||
val guessedString =
|
||||
if (derived) ""
|
||||
else guessed.map(g => "\n Did you mean " + display.show(g) + " ?").toList.mkString
|
||||
val derivedString =
|
||||
if (derived) ", which is a derived setting that needs this key to be defined in this scope."
|
||||
else ""
|
||||
display.show(u.referencedKey) + " from " + refString + sourceString + derivedString + guessedString
|
||||
}
|
||||
|
||||
private[this] def parenPosString(s: Setting[_]): String =
|
||||
s.positionString match { case None => ""; case Some(s) => " (" + s + ")" }
|
||||
|
||||
def guessIntendedScope(
|
||||
validKeys: Seq[ScopedKey[_]],
|
||||
delegates: ScopeType => Seq[ScopeType],
|
||||
key: ScopedKey[_]
|
||||
): Option[ScopedKey[_]] = {
|
||||
val distances = validKeys.flatMap { validKey =>
|
||||
refinedDistance(delegates, validKey, key).map(dist => (dist, validKey))
|
||||
}
|
||||
distances.sortBy(_._1).map(_._2).headOption
|
||||
}
|
||||
|
||||
def refinedDistance(
|
||||
delegates: ScopeType => Seq[ScopeType],
|
||||
a: ScopedKey[_],
|
||||
b: ScopedKey[_]
|
||||
): Option[Int] =
|
||||
if (a.key != b.key || a == b) None
|
||||
else {
|
||||
val dist = delegates(a.scope).indexOf(b.scope)
|
||||
if (dist < 0) None else Some(dist)
|
||||
}
|
||||
|
||||
final class Uninitialized(val undefined: Seq[Undefined], override val toString: String)
|
||||
extends Exception(toString)
|
||||
|
||||
final class Undefined private[sbt] (val defining: Setting[_], val referencedKey: ScopedKey[_])
|
||||
|
||||
final class RuntimeUndefined(val undefined: Seq[Undefined])
|
||||
extends RuntimeException("References to undefined settings at runtime.") {
|
||||
override def getMessage =
|
||||
super.getMessage + undefined.map { u =>
|
||||
"\n" + u.referencedKey + " referenced from " + u.defining
|
||||
}.mkString
|
||||
}
|
||||
|
||||
def Undefined(defining: Setting[_], referencedKey: ScopedKey[_]): Undefined =
|
||||
new Undefined(defining, referencedKey)
|
||||
|
||||
def Uninitialized(
|
||||
validKeys: Seq[ScopedKey[_]],
|
||||
delegates: ScopeType => Seq[ScopeType],
|
||||
keys: Seq[Undefined],
|
||||
runtime: Boolean
|
||||
)(implicit display: Show[ScopedKey[_]]): Uninitialized = {
|
||||
assert(keys.nonEmpty)
|
||||
val suffix = if (keys.length > 1) "s" else ""
|
||||
val prefix = if (runtime) "Runtime reference" else "Reference"
|
||||
val keysString =
|
||||
keys.map(u => showUndefined(u, validKeys, delegates)).mkString("\n\n ", "\n\n ", "")
|
||||
new Uninitialized(
|
||||
keys,
|
||||
prefix + suffix + " to undefined setting" + suffix + ": " + keysString + "\n "
|
||||
)
|
||||
}
|
||||
|
||||
final class Compiled[T](
|
||||
val key: ScopedKey[T],
|
||||
val dependencies: Iterable[ScopedKey[_]],
|
||||
val settings: Seq[Setting[T]]
|
||||
) {
|
||||
override def toString = showFullKey.show(key)
|
||||
}
|
||||
|
||||
final class Flattened(val key: ScopedKey[_], val dependencies: Iterable[ScopedKey[_]])
|
||||
|
||||
def flattenLocals(compiled: CompiledMap): Map[ScopedKey[_], Flattened] = {
|
||||
val locals = compiled flatMap {
|
||||
case (key, comp) =>
|
||||
if (key.key.isLocal) Seq(comp)
|
||||
else nilSeq[Compiled[_]]
|
||||
}
|
||||
val ordered = Dag.topologicalSort(locals)(
|
||||
_.dependencies.flatMap(
|
||||
dep =>
|
||||
if (dep.key.isLocal) Seq[Compiled[_]](compiled(dep))
|
||||
else nilSeq[Compiled[_]]
|
||||
)
|
||||
)
|
||||
def flatten(
|
||||
cmap: Map[ScopedKey[_], Flattened],
|
||||
key: ScopedKey[_],
|
||||
deps: Iterable[ScopedKey[_]]
|
||||
): Flattened =
|
||||
new Flattened(
|
||||
key,
|
||||
deps.flatMap(
|
||||
dep => if (dep.key.isLocal) cmap(dep).dependencies else Seq[ScopedKey[_]](dep).toIterable
|
||||
)
|
||||
)
|
||||
|
||||
val empty = Map.empty[ScopedKey[_], Flattened]
|
||||
|
||||
val flattenedLocals = ordered.foldLeft(empty) { (cmap, c) =>
|
||||
cmap.updated(c.key, flatten(cmap, c.key, c.dependencies))
|
||||
}
|
||||
|
||||
compiled flatMap {
|
||||
case (key, comp) =>
|
||||
if (key.key.isLocal) nilSeq[(ScopedKey[_], Flattened)]
|
||||
else
|
||||
Seq[(ScopedKey[_], Flattened)]((key, flatten(flattenedLocals, key, comp.dependencies)))
|
||||
}
|
||||
}
|
||||
|
||||
def definedAtString(settings: Seq[Setting[_]]): String = {
|
||||
val posDefined = settings.flatMap(_.positionString.toList)
|
||||
if (posDefined.nonEmpty) {
|
||||
val header =
|
||||
if (posDefined.size == settings.size) "defined at:"
|
||||
else
|
||||
"some of the defining occurrences:"
|
||||
header + (posDefined.distinct mkString ("\n\t", "\n\t", "\n"))
|
||||
} else ""
|
||||
}
|
||||
|
||||
/**
|
||||
* The intersect method was calling Seq.contains which is very slow compared
|
||||
* to converting the Seq to a Set and calling contains on the Set. This
|
||||
* private trait abstracts out the two ways that Seq[ScopeType] was actually
|
||||
* used, `contains` and `exists`. In mkDelegates, we can create and cache
|
||||
* instances of Delegates so that we don't have to repeatedly convert the
|
||||
* same Seq to Set. On a 2020 16" macbook pro, creating the compiled map
|
||||
* for the sbt project is roughly 2 seconds faster after this change
|
||||
* (about 3.5 seconds before compared to about 1.5 seconds after)
|
||||
*
|
||||
*/
|
||||
private trait Delegates {
|
||||
def contains(s: ScopeType): Boolean
|
||||
def exists(f: ScopeType => Boolean): Boolean
|
||||
}
|
||||
private[this] def mkDelegates(delegates: ScopeType => Seq[ScopeType]): ScopeType => Delegates = {
|
||||
val delegateMap = new java.util.concurrent.ConcurrentHashMap[ScopeType, Delegates]
|
||||
s =>
|
||||
delegateMap.get(s) match {
|
||||
case null =>
|
||||
val seq = delegates(s)
|
||||
val set = seq.toSet
|
||||
val d = new Delegates {
|
||||
override def contains(s: ScopeType): Boolean = set.contains(s)
|
||||
override def exists(f: ScopeType => Boolean): Boolean = seq.exists(f)
|
||||
}
|
||||
delegateMap.put(s, d)
|
||||
d
|
||||
case d => d
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersects two scopes, returning the more specific one if they intersect, or None otherwise.
|
||||
*/
|
||||
private[sbt] def intersect(s1: ScopeType, s2: ScopeType)(
|
||||
implicit delegates: ScopeType => Seq[ScopeType]
|
||||
): Option[ScopeType] = intersectDelegates(s1, s2, mkDelegates(delegates))
|
||||
|
||||
/**
|
||||
* Intersects two scopes, returning the more specific one if they intersect, or None otherwise.
|
||||
*/
|
||||
private def intersectDelegates(
|
||||
s1: ScopeType,
|
||||
s2: ScopeType,
|
||||
delegates: ScopeType => Delegates
|
||||
): Option[ScopeType] =
|
||||
if (delegates(s1).contains(s2)) Some(s1) // s1 is more specific
|
||||
else if (delegates(s2).contains(s1)) Some(s2) // s2 is more specific
|
||||
else None
|
||||
|
||||
private[this] def deriveAndLocal(init: Seq[Setting[_]], delegates: ScopeType => Delegates)(
|
||||
implicit scopeLocal: ScopeLocal
|
||||
): Seq[Setting[_]] = {
|
||||
import collection.mutable
|
||||
|
||||
final class Derived(val setting: DerivedSetting[_]) {
|
||||
val dependencies = setting.dependencies.map(_.key)
|
||||
def triggeredBy = dependencies.filter(setting.trigger)
|
||||
val inScopes = new mutable.HashSet[ScopeType]
|
||||
val outputs = new mutable.ListBuffer[Setting[_]]
|
||||
}
|
||||
|
||||
final class Deriveds(val key: AttributeKey[_], val settings: mutable.ListBuffer[Derived]) {
|
||||
def dependencies = settings.flatMap(_.dependencies)
|
||||
// This is mainly for use in the cyclic reference error message
|
||||
override def toString =
|
||||
s"Derived settings for ${key.label}, ${definedAtString(settings.map(_.setting).toSeq)}"
|
||||
}
|
||||
|
||||
// separate `derived` settings from normal settings (`defs`)
|
||||
val (derived, rawDefs) =
|
||||
Util.separate[Setting[_], Derived, Setting[_]](init) {
|
||||
case d: DerivedSetting[_] => Left(new Derived(d)); case s => Right(s)
|
||||
}
|
||||
val defs = addLocal(rawDefs)(scopeLocal)
|
||||
|
||||
// group derived settings by the key they define
|
||||
val derivsByDef = new mutable.HashMap[AttributeKey[_], Deriveds]
|
||||
for (s <- derived) {
|
||||
val key = s.setting.key.key
|
||||
derivsByDef.getOrElseUpdate(key, new Deriveds(key, new mutable.ListBuffer)).settings += s
|
||||
}
|
||||
|
||||
// index derived settings by triggering key. This maps a key to the list of settings potentially derived from it.
|
||||
val derivedBy = new mutable.HashMap[AttributeKey[_], mutable.ListBuffer[Derived]]
|
||||
for (s <- derived; d <- s.triggeredBy)
|
||||
derivedBy.getOrElseUpdate(d, new mutable.ListBuffer) += s
|
||||
|
||||
// Map a DerivedSetting[_] to the `Derived` struct wrapping it. Used to ultimately replace a DerivedSetting with
|
||||
// the `Setting`s that were actually derived from it: `Derived.outputs`
|
||||
val derivedToStruct: Map[DerivedSetting[_], Derived] = (derived map { s =>
|
||||
s.setting -> s
|
||||
}).toMap
|
||||
|
||||
// set of defined scoped keys, used to ensure a derived setting is only added if all dependencies are present
|
||||
val defined = new mutable.HashSet[ScopedKey[_]]
|
||||
def addDefs(ss: Seq[Setting[_]]): Unit = { for (s <- ss) defined += s.key }
|
||||
addDefs(defs)
|
||||
|
||||
// true iff the scoped key is in `defined`, taking delegation into account
|
||||
def isDefined(key: AttributeKey[_], scope: ScopeType) =
|
||||
delegates(scope).exists(s => defined.contains(ScopedKey(s, key)))
|
||||
|
||||
// true iff all dependencies of derived setting `d` have a value (potentially via delegation) in `scope`
|
||||
def allDepsDefined(d: Derived, scope: ScopeType, local: Set[AttributeKey[_]]): Boolean =
|
||||
d.dependencies.forall(dep => local(dep) || isDefined(dep, scope))
|
||||
|
||||
// Returns the list of injectable derived settings and their local settings for `sk`.
|
||||
// The settings are to be injected under `outputScope` = whichever scope is more specific of:
|
||||
// * the dependency's (`sk`) scope
|
||||
// * the DerivedSetting's scope in which it has been declared, `definingScope`
|
||||
// provided that these two scopes intersect.
|
||||
// A derived setting is injectable if:
|
||||
// 1. it has not been previously injected into outputScope
|
||||
// 2. it applies to outputScope (as determined by its `filter`)
|
||||
// 3. all of its dependencies are defined for outputScope (allowing for delegation)
|
||||
// This needs to handle local settings because a derived setting wouldn't be injected if it's local setting didn't exist yet.
|
||||
val deriveFor = (sk: ScopedKey[_]) => {
|
||||
val derivedForKey: List[Derived] = derivedBy.get(sk.key).toList.flatten
|
||||
val scope = sk.scope
|
||||
def localAndDerived(d: Derived): Seq[Setting[_]] = {
|
||||
def definingScope = d.setting.key.scope
|
||||
val outputScope = intersectDelegates(scope, definingScope, delegates)
|
||||
outputScope collect {
|
||||
case s if !d.inScopes.contains(s) && d.setting.filter(s) =>
|
||||
val local = d.dependencies.flatMap(dep => scopeLocal(ScopedKey(s, dep)))
|
||||
if (allDepsDefined(d, s, local.map(_.key.key).toSet)) {
|
||||
d.inScopes.add(s)
|
||||
val out = local :+ d.setting.setScope(s)
|
||||
d.outputs ++= out
|
||||
out
|
||||
} else
|
||||
nilSeq
|
||||
} getOrElse nilSeq
|
||||
}
|
||||
derivedForKey.flatMap(localAndDerived)
|
||||
}
|
||||
|
||||
val processed = new mutable.HashSet[ScopedKey[_]]
|
||||
|
||||
// derives settings, transitively so that a derived setting can trigger another
|
||||
def process(rem: List[Setting[_]]): Unit = rem match {
|
||||
case s :: ss =>
|
||||
val sk = s.key
|
||||
val ds = if (processed.add(sk)) deriveFor(sk) else nil
|
||||
addDefs(ds)
|
||||
process(ds ::: ss)
|
||||
case Nil =>
|
||||
}
|
||||
process(defs.toList)
|
||||
|
||||
// Take all the original defs and DerivedSettings along with locals, replace each DerivedSetting with the actual
|
||||
// settings that were derived.
|
||||
val allDefs = addLocal(init)(scopeLocal)
|
||||
allDefs.flatMap {
|
||||
case d: DerivedSetting[_] => (derivedToStruct get d map (_.outputs)).toSeq.flatten
|
||||
case s => s :: nil
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstractly defines a value of type `T`.
|
||||
*
|
||||
* Specifically it defines a node in a task graph,
|
||||
* where the `dependencies` represents dependent nodes,
|
||||
* and `evaluate` represents the calculation based on the existing body of knowledge.
|
||||
*
|
||||
* @tparam T the type of the value this defines.
|
||||
*/
|
||||
sealed trait Initialize[T] {
|
||||
def dependencies: Seq[ScopedKey[_]]
|
||||
def apply[S](g: T => S): Initialize[S]
|
||||
|
||||
private[sbt] def mapReferenced(g: MapScoped): Initialize[T]
|
||||
private[sbt] def mapConstant(g: MapConstant): Initialize[T]
|
||||
|
||||
private[sbt] def validateReferenced(g: ValidateRef): ValidatedInit[T] =
|
||||
validateKeyReferenced(new ValidateKeyRef {
|
||||
def apply[B](key: ScopedKey[B], selfRefOk: Boolean) = g(key)
|
||||
})
|
||||
|
||||
private[sbt] def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[T]
|
||||
|
||||
def evaluate(map: Settings[ScopeType]): T
|
||||
def zip[S](o: Initialize[S]): Initialize[(T, S)] = zipTupled(o)(idFun)
|
||||
def zipWith[S, U](o: Initialize[S])(f: (T, S) => U): Initialize[U] = zipTupled(o)(f.tupled)
|
||||
private[this] def zipTupled[S, U](o: Initialize[S])(f: ((T, S)) => U): Initialize[U] =
|
||||
new Apply[λ[L[x] => (L[T], L[S])], U](f, (this, o), AList.tuple2[T, S])
|
||||
|
||||
/** A fold on the static attributes of this and nested Initializes. */
|
||||
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S
|
||||
}
|
||||
|
||||
object Initialize {
|
||||
implicit def joinInitialize[T](s: Seq[Initialize[T]]): JoinInitSeq[T] = new JoinInitSeq(s)
|
||||
|
||||
final class JoinInitSeq[T](s: Seq[Initialize[T]]) {
|
||||
def joinWith[S](f: Seq[T] => S): Initialize[S] = uniform(s)(f)
|
||||
def join: Initialize[Seq[T]] = uniform(s)(idFun)
|
||||
}
|
||||
|
||||
def join[T](inits: Seq[Initialize[T]]): Initialize[Seq[T]] = uniform(inits)(idFun)
|
||||
|
||||
def joinAny[M[_]](inits: Seq[Initialize[M[T]] forSome { type T }]): Initialize[Seq[M[_]]] =
|
||||
join(inits.asInstanceOf[Seq[Initialize[M[_]]]])
|
||||
}
|
||||
|
||||
object SettingsDefinition {
|
||||
implicit def unwrapSettingsDefinition(d: SettingsDefinition): Seq[Setting[_]] = d.settings
|
||||
implicit def wrapSettingsDefinition(ss: Seq[Setting[_]]): SettingsDefinition =
|
||||
new SettingList(ss)
|
||||
}
|
||||
|
||||
sealed trait SettingsDefinition {
|
||||
def settings: Seq[Setting[_]]
|
||||
}
|
||||
|
||||
final class SettingList(val settings: Seq[Setting[_]]) extends SettingsDefinition
|
||||
|
||||
sealed class Setting[T] private[Init] (
|
||||
val key: ScopedKey[T],
|
||||
val init: Initialize[T],
|
||||
val pos: SourcePosition
|
||||
) extends SettingsDefinition {
|
||||
def settings = this :: Nil
|
||||
def definitive: Boolean = !init.dependencies.contains(key)
|
||||
def dependencies: Seq[ScopedKey[_]] =
|
||||
remove(init.dependencies.asInstanceOf[Seq[ScopedKey[T]]], key)
|
||||
def mapReferenced(g: MapScoped): Setting[T] = make(key, init mapReferenced g, pos)
|
||||
|
||||
def validateReferenced(g: ValidateRef): Either[Seq[Undefined], Setting[T]] =
|
||||
(init validateReferenced g).right.map(newI => make(key, newI, pos))
|
||||
|
||||
private[sbt] def validateKeyReferenced(g: ValidateKeyRef): Either[Seq[Undefined], Setting[T]] =
|
||||
(init validateKeyReferenced g).right.map(newI => make(key, newI, pos))
|
||||
|
||||
def mapKey(g: MapScoped): Setting[T] = make(g(key), init, pos)
|
||||
def mapInit(f: (ScopedKey[T], T) => T): Setting[T] = make(key, init(t => f(key, t)), pos)
|
||||
def mapConstant(g: MapConstant): Setting[T] = make(key, init mapConstant g, pos)
|
||||
def withPos(pos: SourcePosition) = make(key, init, pos)
|
||||
|
||||
def positionString: Option[String] = pos match {
|
||||
case pos: FilePosition => Some(pos.path + ":" + pos.startLine)
|
||||
case NoPosition => None
|
||||
}
|
||||
|
||||
private[sbt] def mapInitialize(f: Initialize[T] => Initialize[T]): Setting[T] =
|
||||
make(key, f(init), pos)
|
||||
|
||||
override def toString = "setting(" + key + ") at " + pos
|
||||
|
||||
protected[this] def make[B](
|
||||
key: ScopedKey[B],
|
||||
init: Initialize[B],
|
||||
pos: SourcePosition
|
||||
): Setting[B] = new Setting[B](key, init, pos)
|
||||
|
||||
protected[sbt] def isDerived: Boolean = false
|
||||
private[sbt] def setScope(s: ScopeType): Setting[T] =
|
||||
make(key.copy(scope = s), init.mapReferenced(mapScope(const(s))), pos)
|
||||
|
||||
/** Turn this setting into a `DefaultSetting` if it's not already, otherwise returns `this` */
|
||||
private[sbt] def default(id: => Long = nextDefaultID()): DefaultSetting[T] =
|
||||
DefaultSetting(key, init, pos, id)
|
||||
}
|
||||
|
||||
private[Init] sealed class DerivedSetting[T](
|
||||
sk: ScopedKey[T],
|
||||
i: Initialize[T],
|
||||
p: SourcePosition,
|
||||
val filter: ScopeType => Boolean,
|
||||
val trigger: AttributeKey[_] => Boolean
|
||||
) extends Setting[T](sk, i, p) {
|
||||
|
||||
override def make[B](key: ScopedKey[B], init: Initialize[B], pos: SourcePosition): Setting[B] =
|
||||
new DerivedSetting[B](key, init, pos, filter, trigger)
|
||||
|
||||
protected[sbt] override def isDerived: Boolean = true
|
||||
|
||||
override def default(_id: => Long): DefaultSetting[T] =
|
||||
new DerivedSetting[T](sk, i, p, filter, trigger) with DefaultSetting[T] { val id = _id }
|
||||
|
||||
override def toString = "derived " + super.toString
|
||||
}
|
||||
|
||||
// Only keep the first occurrence of this setting and move it to the front so that it has lower precedence than non-defaults.
|
||||
// This is intended for internal sbt use only, where alternatives like Plugin.globalSettings are not available.
|
||||
private[Init] sealed trait DefaultSetting[T] extends Setting[T] {
|
||||
val id: Long
|
||||
|
||||
override def make[B](key: ScopedKey[B], init: Initialize[B], pos: SourcePosition): Setting[B] =
|
||||
super.make(key, init, pos) default id
|
||||
|
||||
override final def hashCode = id.hashCode
|
||||
|
||||
override final def equals(o: Any): Boolean = o match {
|
||||
case d: DefaultSetting[_] => d.id == id; case _ => false
|
||||
}
|
||||
|
||||
override def toString = s"default($id) " + super.toString
|
||||
override def default(id: => Long) = this
|
||||
}
|
||||
|
||||
object DefaultSetting {
|
||||
def apply[T](sk: ScopedKey[T], i: Initialize[T], p: SourcePosition, _id: Long) =
|
||||
new Setting[T](sk, i, p) with DefaultSetting[T] { val id = _id }
|
||||
}
|
||||
|
||||
private[this] def handleUndefined[T](vr: ValidatedInit[T]): Initialize[T] = vr match {
|
||||
case Left(undefs) => throw new RuntimeUndefined(undefs)
|
||||
case Right(x) => x
|
||||
}
|
||||
|
||||
private[this] lazy val getValidated = λ[ValidatedInit ~> Initialize](handleUndefined(_))
|
||||
|
||||
// mainly for reducing generated class count
|
||||
private[this] def validateKeyReferencedT(g: ValidateKeyRef) =
|
||||
λ[Initialize ~> ValidatedInit](_ validateKeyReferenced g)
|
||||
|
||||
private[this] def mapReferencedT(g: MapScoped) = λ[Initialize ~> Initialize](_ mapReferenced g)
|
||||
private[this] def mapConstantT(g: MapConstant) = λ[Initialize ~> Initialize](_ mapConstant g)
|
||||
private[this] def evaluateT(g: Settings[ScopeType]) = λ[Initialize ~> Id](_ evaluate g)
|
||||
|
||||
private[this] def deps(ls: Seq[Initialize[_]]): Seq[ScopedKey[_]] = ls.flatMap(_.dependencies)
|
||||
|
||||
/** An `Initialize[T]` associated with a `ScopedKey[S]`.
|
||||
* @tparam S the type of the associated `ScopedKey`
|
||||
* @tparam T the type of the value this `Initialize` defines.
|
||||
*/
|
||||
sealed trait Keyed[S, T] extends Initialize[T] {
|
||||
def scopedKey: ScopedKey[S]
|
||||
def transform: S => T
|
||||
|
||||
final def dependencies = scopedKey :: Nil
|
||||
final def apply[Z](g: T => Z): Initialize[Z] = new GetValue(scopedKey, g compose transform)
|
||||
final def evaluate(ss: Settings[ScopeType]): T = transform(getValue(ss, scopedKey))
|
||||
final def mapReferenced(g: MapScoped): Initialize[T] = new GetValue(g(scopedKey), transform)
|
||||
|
||||
private[sbt] final def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[T] =
|
||||
g(scopedKey, false) match {
|
||||
case Left(un) => Left(un :: Nil)
|
||||
case Right(nk) => Right(new GetValue(nk, transform))
|
||||
}
|
||||
|
||||
final def mapConstant(g: MapConstant): Initialize[T] = g(scopedKey) match {
|
||||
case None => this
|
||||
case Some(const) => new Value(() => transform(const))
|
||||
}
|
||||
|
||||
private[sbt] def processAttributes[B](init: B)(f: (B, AttributeMap) => B): B = init
|
||||
}
|
||||
|
||||
private[this] final class GetValue[S, T](val scopedKey: ScopedKey[S], val transform: S => T)
|
||||
extends Keyed[S, T]
|
||||
|
||||
/** A `Keyed` where the type of the value and the associated `ScopedKey` are the same.
|
||||
* @tparam T the type of both the value this `Initialize` defines and the type of the associated `ScopedKey`.
|
||||
*/
|
||||
trait KeyedInitialize[T] extends Keyed[T, T] {
|
||||
final val transform = idFun[T]
|
||||
}
|
||||
|
||||
private[sbt] final class TransformCapture(val f: Initialize ~> Initialize)
|
||||
extends Initialize[Initialize ~> Initialize] {
|
||||
def dependencies = Nil
|
||||
def apply[Z](g2: (Initialize ~> Initialize) => Z): Initialize[Z] = map(this)(g2)
|
||||
def evaluate(ss: Settings[ScopeType]): Initialize ~> Initialize = f
|
||||
def mapReferenced(g: MapScoped) = new TransformCapture(mapReferencedT(g) ∙ f)
|
||||
def mapConstant(g: MapConstant) = new TransformCapture(mapConstantT(g) ∙ f)
|
||||
|
||||
def validateKeyReferenced(g: ValidateKeyRef) =
|
||||
Right(new TransformCapture(getValidated ∙ validateKeyReferencedT(g) ∙ f))
|
||||
|
||||
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
|
||||
}
|
||||
|
||||
private[sbt] final class ValidationCapture[T](val key: ScopedKey[T], val selfRefOk: Boolean)
|
||||
extends Initialize[ScopedKey[T]] {
|
||||
def dependencies = Nil
|
||||
def apply[Z](g2: ScopedKey[T] => Z): Initialize[Z] = map(this)(g2)
|
||||
def evaluate(ss: Settings[ScopeType]) = key
|
||||
def mapReferenced(g: MapScoped) = new ValidationCapture(g(key), selfRefOk)
|
||||
def mapConstant(g: MapConstant) = this
|
||||
|
||||
def validateKeyReferenced(g: ValidateKeyRef) = g(key, selfRefOk) match {
|
||||
case Left(un) => Left(un :: Nil)
|
||||
case Right(k) => Right(new ValidationCapture(k, selfRefOk))
|
||||
}
|
||||
|
||||
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
|
||||
}
|
||||
|
||||
private[sbt] final class Bind[S, T](val f: S => Initialize[T], val in: Initialize[S])
|
||||
extends Initialize[T] {
|
||||
def dependencies = in.dependencies
|
||||
def apply[Z](g: T => Z): Initialize[Z] = new Bind[S, Z](s => f(s)(g), in)
|
||||
def evaluate(ss: Settings[ScopeType]): T = f(in evaluate ss) evaluate ss
|
||||
def mapReferenced(g: MapScoped) = new Bind[S, T](s => f(s) mapReferenced g, in mapReferenced g)
|
||||
|
||||
def validateKeyReferenced(g: ValidateKeyRef) = (in validateKeyReferenced g).right.map {
|
||||
validIn =>
|
||||
new Bind[S, T](s => handleUndefined(f(s) validateKeyReferenced g), validIn)
|
||||
}
|
||||
|
||||
def mapConstant(g: MapConstant) = new Bind[S, T](s => f(s) mapConstant g, in mapConstant g)
|
||||
|
||||
private[sbt] def processAttributes[B](init: B)(f: (B, AttributeMap) => B): B =
|
||||
in.processAttributes(init)(f)
|
||||
}
|
||||
|
||||
private[sbt] final class Optional[S, T](val a: Option[Initialize[S]], val f: Option[S] => T)
|
||||
extends Initialize[T] {
|
||||
def dependencies = deps(a.toList)
|
||||
def apply[Z](g: T => Z): Initialize[Z] = new Optional[S, Z](a, g compose f)
|
||||
def mapReferenced(g: MapScoped) = new Optional(a map mapReferencedT(g).fn, f)
|
||||
|
||||
def validateKeyReferenced(g: ValidateKeyRef) = a match {
|
||||
case None => Right(this)
|
||||
case Some(i) => Right(new Optional(i.validateKeyReferenced(g).right.toOption, f))
|
||||
}
|
||||
|
||||
def mapConstant(g: MapConstant): Initialize[T] = new Optional(a map mapConstantT(g).fn, f)
|
||||
def evaluate(ss: Settings[ScopeType]): T = f(a.flatMap(i => trapBadRef(evaluateT(ss)(i))))
|
||||
|
||||
// proper solution is for evaluate to be deprecated or for external use only and a new internal method returning Either be used
|
||||
private[this] def trapBadRef[A](run: => A): Option[A] =
|
||||
try Some(run)
|
||||
catch { case _: InvalidReference => None }
|
||||
|
||||
private[sbt] def processAttributes[B](init: B)(f: (B, AttributeMap) => B): B = a match {
|
||||
case None => init
|
||||
case Some(i) => i.processAttributes(init)(f)
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] final class Value[T](val value: () => T) extends Initialize[T] {
|
||||
def dependencies = Nil
|
||||
def mapReferenced(g: MapScoped) = this
|
||||
def validateKeyReferenced(g: ValidateKeyRef) = Right(this)
|
||||
def apply[S](g: T => S) = new Value[S](() => g(value()))
|
||||
def mapConstant(g: MapConstant) = this
|
||||
def evaluate(map: Settings[ScopeType]): T = value()
|
||||
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
|
||||
}
|
||||
|
||||
private[sbt] final object StaticScopes extends Initialize[Set[ScopeType]] {
|
||||
def dependencies = Nil
|
||||
def mapReferenced(g: MapScoped) = this
|
||||
def validateKeyReferenced(g: ValidateKeyRef) = Right(this)
|
||||
def apply[S](g: Set[ScopeType] => S) = map(this)(g)
|
||||
def mapConstant(g: MapConstant) = this
|
||||
def evaluate(map: Settings[ScopeType]) = map.scopes
|
||||
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
|
||||
}
|
||||
|
||||
private[sbt] final class Apply[K[L[x]], T](
|
||||
val f: K[Id] => T,
|
||||
val inputs: K[Initialize],
|
||||
val alist: AList[K]
|
||||
) extends Initialize[T] {
|
||||
def dependencies = deps(alist.toList(inputs))
|
||||
def mapReferenced(g: MapScoped) = mapInputs(mapReferencedT(g))
|
||||
def apply[S](g: T => S) = new Apply(g compose f, inputs, alist)
|
||||
def mapConstant(g: MapConstant) = mapInputs(mapConstantT(g))
|
||||
|
||||
def mapInputs(g: Initialize ~> Initialize): Initialize[T] =
|
||||
new Apply(f, alist.transform(inputs, g), alist)
|
||||
|
||||
def evaluate(ss: Settings[ScopeType]) = f(alist.transform(inputs, evaluateT(ss)))
|
||||
|
||||
def validateKeyReferenced(g: ValidateKeyRef) = {
|
||||
val tx = alist.transform(inputs, validateKeyReferencedT(g))
|
||||
val undefs = alist.toList(tx).flatMap(_.left.toSeq.flatten)
|
||||
val get = λ[ValidatedInit ~> Initialize](_.right.get)
|
||||
if (undefs.isEmpty) Right(new Apply(f, alist.transform(tx, get), alist)) else Left(undefs)
|
||||
}
|
||||
|
||||
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S =
|
||||
alist.toList(inputs).foldLeft(init) { (v, i) =>
|
||||
i.processAttributes(v)(f)
|
||||
}
|
||||
}
|
||||
private def remove[T](s: Seq[T], v: T) = s filterNot (_ == v)
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import sun.misc.{ Signal, SignalHandler }
|
||||
|
||||
object Signals {
|
||||
val CONT = "CONT"
|
||||
val INT = "INT"
|
||||
|
||||
def withHandler[T](handler: () => Unit, signal: String = INT)(action: () => T): T = {
|
||||
val result =
|
||||
try {
|
||||
val signals = new Signals0
|
||||
signals.withHandler(signal, handler, action)
|
||||
} catch { case _: LinkageError => Right(action()): Either[Throwable, T] }
|
||||
|
||||
result match {
|
||||
case Left(e) => throw e
|
||||
case Right(v) => v
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper interface so we can expose internals of signal-isms to others. */
|
||||
sealed trait Registration {
|
||||
def remove(): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a signal handler that can be removed later.
|
||||
* NOTE: Does not stack with other signal handlers!!!!
|
||||
*/
|
||||
def register(handler: () => Unit, signal: String = INT): Registration =
|
||||
// TODO - Maybe we can just ignore things if not is-supported.
|
||||
if (supported(signal)) {
|
||||
val intSignal = new Signal(signal)
|
||||
val newHandler = new SignalHandler {
|
||||
def handle(sig: Signal): Unit = { handler() }
|
||||
}
|
||||
val oldHandler = Signal.handle(intSignal, newHandler)
|
||||
new UnregisterNewHandler(intSignal, oldHandler)
|
||||
} else {
|
||||
// TODO - Maybe we should just throw an exception if we don't support signals...
|
||||
NullUnregisterNewHandler
|
||||
}
|
||||
|
||||
def supported(signal: String): Boolean =
|
||||
try {
|
||||
val signals = new Signals0
|
||||
signals.supported(signal)
|
||||
} catch { case _: LinkageError => false }
|
||||
}
|
||||
|
||||
private class UnregisterNewHandler(intSignal: Signal, oldHandler: SignalHandler)
|
||||
extends Signals.Registration {
|
||||
override def remove(): Unit = {
|
||||
Signal.handle(intSignal, oldHandler)
|
||||
()
|
||||
}
|
||||
}
|
||||
private object NullUnregisterNewHandler extends Signals.Registration {
|
||||
override def remove(): Unit = ()
|
||||
}
|
||||
|
||||
// Must only be referenced using a
|
||||
// try { } catch { case _: LinkageError => ... }
|
||||
// block to
|
||||
private final class Signals0 {
|
||||
def supported(signal: String): Boolean = {
|
||||
import sun.misc.Signal
|
||||
try {
|
||||
new Signal(signal); true
|
||||
} catch { case _: IllegalArgumentException => false }
|
||||
}
|
||||
|
||||
// returns a LinkageError in `action` as Left(t) in order to avoid it being
|
||||
// incorrectly swallowed as missing Signal/SignalHandler
|
||||
def withHandler[T](signal: String, handler: () => Unit, action: () => T): Either[Throwable, T] = {
|
||||
import sun.misc.{ Signal, SignalHandler }
|
||||
val intSignal = new Signal(signal)
|
||||
val newHandler = new SignalHandler {
|
||||
def handle(sig: Signal): Unit = { handler() }
|
||||
}
|
||||
|
||||
val oldHandler = Signal.handle(intSignal, newHandler)
|
||||
|
||||
try Right(action())
|
||||
catch { case e: LinkageError => Left(e) } finally {
|
||||
Signal.handle(intSignal, oldHandler); ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
trait TypeFunctions {
|
||||
import TypeFunctions._
|
||||
type Id[X] = X
|
||||
type NothingK[X] = Nothing
|
||||
sealed trait Const[A] { type Apply[B] = A }
|
||||
sealed trait ConstK[A] { type l[L[x]] = A }
|
||||
sealed trait Compose[A[_], B[_]] { type Apply[T] = A[B[T]] }
|
||||
sealed trait ∙[A[_], B[_]] { type l[T] = A[B[T]] }
|
||||
private type AnyLeft[T] = Left[T, Nothing]
|
||||
private type AnyRight[T] = Right[Nothing, T]
|
||||
|
||||
final val left: Id ~> Left[*, Nothing] =
|
||||
λ[Id ~> AnyLeft](Left(_)).setToString("TypeFunctions.left")
|
||||
final val right: Id ~> Right[Nothing, *] =
|
||||
λ[Id ~> AnyRight](Right(_)).setToString("TypeFunctions.right")
|
||||
final val some: Id ~> Some[*] = λ[Id ~> Some](Some(_)).setToString("TypeFunctions.some")
|
||||
final def idFun[T]: T => T = ((t: T) => t).setToString("TypeFunctions.id")
|
||||
final def const[A, B](b: B): A => B = ((_: A) => b).setToString(s"TypeFunctions.const($b)")
|
||||
final def idK[M[_]]: M ~> M = λ[M ~> M](m => m).setToString("TypeFunctions.idK")
|
||||
|
||||
def nestCon[M[_], N[_], G[_]](f: M ~> N): (M ∙ G)#l ~> (N ∙ G)#l =
|
||||
f.asInstanceOf[(M ∙ G)#l ~> (N ∙ G)#l] // implemented with a cast to avoid extra object+method call.
|
||||
// castless version:
|
||||
// λ[(M ∙ G)#l ~> (N ∙ G)#l](f(_))
|
||||
|
||||
type Endo[T] = T => T
|
||||
type ~>|[A[_], B[_]] = A ~> Compose[Option, B]#Apply
|
||||
}
|
||||
|
||||
object TypeFunctions extends TypeFunctions {
|
||||
private implicit class Ops[T[_], R[_]](val underlying: T ~> R) extends AnyVal {
|
||||
def setToString(string: String): T ~> R = new (T ~> R) {
|
||||
override def apply[U](a: T[U]): R[U] = underlying(a)
|
||||
override def toString: String = string
|
||||
override def equals(o: Any): Boolean = underlying.equals(o)
|
||||
override def hashCode: Int = underlying.hashCode
|
||||
}
|
||||
}
|
||||
private implicit class FunctionOps[A, B](val f: A => B) extends AnyVal {
|
||||
def setToString(string: String): A => B = new (A => B) {
|
||||
override def apply(a: A): B = f(a)
|
||||
override def toString: String = string
|
||||
override def equals(o: Any): Boolean = f.equals(o)
|
||||
override def hashCode: Int = f.hashCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ~>[-A[_], +B[_]] { outer =>
|
||||
def apply[T](a: A[T]): B[T]
|
||||
// directly on ~> because of type inference limitations
|
||||
final def ∙[C[_]](g: C ~> A): C ~> B = λ[C ~> B](c => outer.apply(g(c)))
|
||||
final def ∙[C, D](g: C => D)(implicit ev: D <:< A[D]): C => B[D] = i => apply(ev(g(i)))
|
||||
final def fn[T]: A[T] => B[T] = (t: A[T]) => apply[T](t)
|
||||
}
|
||||
|
||||
object ~> {
|
||||
import TypeFunctions._
|
||||
val Id: Id ~> Id = idK[Id]
|
||||
implicit def tcIdEquals: Id ~> Id = Id
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
object Types extends Types
|
||||
|
||||
trait Types extends TypeFunctions {
|
||||
val :^: = KCons
|
||||
type :+:[H, T <: HList] = HCons[H, T]
|
||||
val :+: = HCons
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
import scala.reflect.macros.blackbox
|
||||
import scala.language.experimental.macros
|
||||
|
||||
object Util {
|
||||
def makeList[T](size: Int, value: T): List[T] = List.fill(size)(value)
|
||||
|
||||
def separateE[A, B](ps: Seq[Either[A, B]]): (Seq[A], Seq[B]) =
|
||||
separate(ps)(Types.idFun)
|
||||
|
||||
def separate[T, A, B](ps: Seq[T])(f: T => Either[A, B]): (Seq[A], Seq[B]) = {
|
||||
val (a, b) = ps.foldLeft((Nil: Seq[A], Nil: Seq[B]))((xs, y) => prependEither(xs, f(y)))
|
||||
(a.reverse, b.reverse)
|
||||
}
|
||||
|
||||
def prependEither[A, B](acc: (Seq[A], Seq[B]), next: Either[A, B]): (Seq[A], Seq[B]) =
|
||||
next match {
|
||||
case Left(l) => (l +: acc._1, acc._2)
|
||||
case Right(r) => (acc._1, r +: acc._2)
|
||||
}
|
||||
|
||||
def pairID[A, B] = (a: A, b: B) => (a, b)
|
||||
|
||||
private[this] lazy val Hyphen = """-(\p{javaLowerCase})""".r
|
||||
|
||||
def hasHyphen(s: String): Boolean = s.indexOf('-') >= 0
|
||||
|
||||
def hyphenToCamel(s: String): String =
|
||||
if (hasHyphen(s)) Hyphen.replaceAllIn(s, _.group(1).toUpperCase(Locale.ENGLISH)) else s
|
||||
|
||||
private[this] lazy val Camel = """(\p{javaLowerCase})(\p{javaUpperCase})""".r
|
||||
|
||||
def camelToHyphen(s: String): String =
|
||||
Camel.replaceAllIn(s, m => m.group(1) + "-" + m.group(2).toLowerCase(Locale.ENGLISH))
|
||||
|
||||
def quoteIfKeyword(s: String): String = if (ScalaKeywords.values(s)) '`' + s + '`' else s
|
||||
|
||||
def ignoreResult[T](f: => T): Unit = macro Macro.ignore
|
||||
|
||||
lazy val isMac: Boolean =
|
||||
System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac")
|
||||
|
||||
lazy val isWindows: Boolean =
|
||||
System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")
|
||||
|
||||
lazy val isCygwin: Boolean = {
|
||||
val os = Option(System.getenv("OSTYPE"))
|
||||
os match {
|
||||
case Some(x) => x.toLowerCase(Locale.ENGLISH).contains("cygwin")
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
lazy val isNonCygwinWindows: Boolean = isWindows && !isCygwin
|
||||
lazy val isCygwinWindows: Boolean = isWindows && isCygwin
|
||||
|
||||
lazy val isEmacs: Boolean = Option(System.getenv("INSIDE_EMACS")).isDefined
|
||||
|
||||
def nil[A]: List[A] = List.empty[A]
|
||||
def nilSeq[A]: Seq[A] = Seq.empty[A]
|
||||
def none[A]: Option[A] = (None: Option[A])
|
||||
|
||||
implicit class AnyOps[A](private val value: A) extends AnyVal {
|
||||
def some: Option[A] = (Some(value): Option[A])
|
||||
}
|
||||
class Macro(val c: blackbox.Context) {
|
||||
def ignore(f: c.Tree): c.Expr[Unit] = c.universe.reify({ c.Expr[Any](f).splice; () })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.util
|
||||
|
||||
import sjsonnew.JsonWriter
|
||||
|
||||
sealed trait OptJsonWriter[A]
|
||||
final case class NoJsonWriter[A]() extends OptJsonWriter[A]
|
||||
final case class SomeJsonWriter[A](value: JsonWriter[A]) extends OptJsonWriter[A]
|
||||
|
||||
trait OptJsonWriter0 {
|
||||
implicit def fallback[A]: NoJsonWriter[A] = NoJsonWriter()
|
||||
}
|
||||
object OptJsonWriter extends OptJsonWriter0 {
|
||||
implicit def lift[A](implicit z: JsonWriter[A]): SomeJsonWriter[A] = SomeJsonWriter(z)
|
||||
|
||||
trait StrictMode0 {
|
||||
implicit def conflictingFallback1[A]: NoJsonWriter[A] = NoJsonWriter()
|
||||
implicit def conflictingFallback2[A]: NoJsonWriter[A] = NoJsonWriter()
|
||||
}
|
||||
object StrictMode extends StrictMode0 {
|
||||
implicit def lift[A](implicit z: JsonWriter[A]): SomeJsonWriter[A] = SomeJsonWriter(z)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.util
|
||||
|
||||
trait Show[A] {
|
||||
def show(a: A): String
|
||||
}
|
||||
object Show {
|
||||
def apply[A](f: A => String): Show[A] = a => f(a)
|
||||
|
||||
def fromToString[A]: Show[A] = _.toString
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
|
||||
import scala.collection.mutable.HashSet
|
||||
|
||||
object DagSpecification extends Properties("Dag") {
|
||||
property("No repeated nodes") = forAll { (dag: TestDag) =>
|
||||
isSet(dag.topologicalSort)
|
||||
}
|
||||
property("Sort contains node") = forAll { (dag: TestDag) =>
|
||||
dag.topologicalSort.contains(dag)
|
||||
}
|
||||
property("Dependencies precede node") = forAll { (dag: TestDag) =>
|
||||
dependenciesPrecedeNodes(dag.topologicalSort)
|
||||
}
|
||||
|
||||
implicit lazy val arbTestDag: Arbitrary[TestDag] = Arbitrary(Gen.sized(dagGen))
|
||||
private def dagGen(nodeCount: Int): Gen[TestDag] = {
|
||||
val nodes = new HashSet[TestDag]
|
||||
def nonterminalGen(p: Gen.Parameters): Gen[TestDag] = {
|
||||
val seed = rng.Seed.random()
|
||||
for {
|
||||
i <- 0 until nodeCount
|
||||
nextDeps <- Gen.someOf(nodes).apply(p, seed)
|
||||
} nodes += new TestDag(i, nextDeps)
|
||||
for (nextDeps <- Gen.someOf(nodes)) yield new TestDag(nodeCount, nextDeps)
|
||||
}
|
||||
Gen.parameterized(nonterminalGen)
|
||||
}
|
||||
|
||||
private def isSet[T](c: Seq[T]) = Set(c: _*).size == c.size
|
||||
private def dependenciesPrecedeNodes(sort: List[TestDag]) = {
|
||||
val seen = new HashSet[TestDag]
|
||||
def iterate(remaining: List[TestDag]): Boolean = {
|
||||
remaining match {
|
||||
case Nil => true
|
||||
case node :: tail =>
|
||||
if (node.dependencies.forall(seen.contains) && !seen.contains(node)) {
|
||||
seen += node
|
||||
iterate(tail)
|
||||
} else
|
||||
false
|
||||
}
|
||||
}
|
||||
iterate(sort)
|
||||
}
|
||||
}
|
||||
class TestDag(id: Int, val dependencies: Iterable[TestDag]) extends Dag[TestDag] {
|
||||
override def toString = id + "->" + dependencies.mkString("[", ",", "]")
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package internal
|
||||
package util
|
||||
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe._
|
||||
import sjsonnew._, BasicJsonProtocol._, support.scalajson.unsafe._
|
||||
import HListFormats._
|
||||
|
||||
class HListFormatSpec extends UnitSpec {
|
||||
val quux = 23 :+: "quux" :+: true :+: HNil
|
||||
|
||||
it should "round trip quux" in assertRoundTrip(quux)
|
||||
it should "round trip hnil" in assertRoundTrip(HNil)
|
||||
|
||||
it should "have a flat structure for quux" in assertJsonString(quux, """[23,"quux",true]""")
|
||||
it should "have a flat structure for hnil" in assertJsonString(HNil, "[]")
|
||||
|
||||
def assertRoundTrip[A: JsonWriter: JsonReader](x: A) = {
|
||||
val jsonString: String = toJsonString(x)
|
||||
val jValue: JValue = Parser.parseUnsafe(jsonString)
|
||||
val y: A = Converter.fromJson[A](jValue).get
|
||||
assert(x === y)
|
||||
}
|
||||
|
||||
def assertJsonString[A: JsonWriter](x: A, s: String) = assert(toJsonString(x) === s)
|
||||
|
||||
def toJsonString[A: JsonWriter](x: A): String = CompactPrinter(Converter.toJson(x).get)
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
|
||||
object KeyTest extends Properties("AttributeKey") {
|
||||
property("equality") = {
|
||||
compare(AttributeKey[Int]("test"), AttributeKey[Int]("test"), true) &&
|
||||
compare(AttributeKey[Int]("test"), AttributeKey[Int]("test", "description"), true) &&
|
||||
compare(AttributeKey[Int]("test", "a"), AttributeKey[Int]("test", "b"), true) &&
|
||||
compare(AttributeKey[Int]("test"), AttributeKey[Int]("tests"), false) &&
|
||||
compare(AttributeKey[Int]("test"), AttributeKey[Double]("test"), false) &&
|
||||
compare(AttributeKey[java.lang.Integer]("test"), AttributeKey[Int]("test"), false) &&
|
||||
compare(AttributeKey[Map[Int, String]]("test"), AttributeKey[Map[Int, String]]("test"), true) &&
|
||||
compare(AttributeKey[Map[Int, String]]("test"), AttributeKey[Map[Int, _]]("test"), false)
|
||||
}
|
||||
|
||||
def compare(a: AttributeKey[_], b: AttributeKey[_], same: Boolean) =
|
||||
("a.label: " + a.label) |:
|
||||
("a.manifest: " + a.manifest) |:
|
||||
("b.label: " + b.label) |:
|
||||
("b.manifest: " + b.manifest) |:
|
||||
("expected equal? " + same) |:
|
||||
compare0(a, b, same)
|
||||
|
||||
def compare0(a: AttributeKey[_], b: AttributeKey[_], same: Boolean) =
|
||||
if (same) {
|
||||
("equality" |: (a == b)) &&
|
||||
("hash" |: (a.hashCode == b.hashCode))
|
||||
} else
|
||||
("equality" |: (a != b))
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import Types._
|
||||
|
||||
// compilation test
|
||||
object PMapTest {
|
||||
val mp = new DelegatingPMap[Some, Id](new collection.mutable.HashMap)
|
||||
mp(Some("asdf")) = "a"
|
||||
mp(Some(3)) = 9
|
||||
val x = Some(3) :^: Some("asdf") :^: KNil
|
||||
val y = x.transform[Id](mp)
|
||||
assert(y.head == 9)
|
||||
assert(y.tail.head == "a")
|
||||
assert(y.tail.tail == KNil)
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import sbt.util.Show
|
||||
|
||||
/** Define our settings system */
|
||||
// A basic scope indexed by an integer.
|
||||
final case class Scope(nestIndex: Int, idAtIndex: Int = 0)
|
||||
|
||||
// Extend the Init trait.
|
||||
// (It is done this way because the Scope type parameter is used everywhere in Init.
|
||||
// Lots of type constructors would become binary, which as you may know requires lots of type lambdas
|
||||
// when you want a type function with only one parameter.
|
||||
// That would be a general pain.)
|
||||
case class SettingsExample() extends Init[Scope] {
|
||||
// Provides a way of showing a Scope+AttributeKey[_]
|
||||
val showFullKey: Show[ScopedKey[_]] = Show[ScopedKey[_]]((key: ScopedKey[_]) => {
|
||||
s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}"
|
||||
})
|
||||
|
||||
// A sample delegation function that delegates to a Scope with a lower index.
|
||||
val delegates: Scope => Seq[Scope] = {
|
||||
case s @ Scope(index, proj) =>
|
||||
s +: (if (index <= 0) Nil
|
||||
else {
|
||||
(if (proj > 0) List(Scope(index)) else Nil) ++: delegates(Scope(index - 1))
|
||||
})
|
||||
}
|
||||
|
||||
// Not using this feature in this example.
|
||||
val scopeLocal: ScopeLocal = _ => Nil
|
||||
|
||||
// These three functions + a scope (here, Scope) are sufficient for defining our settings system.
|
||||
}
|
||||
|
||||
/** Usage Example **/
|
||||
case class SettingsUsage(val settingsExample: SettingsExample) {
|
||||
import settingsExample._
|
||||
|
||||
// Define some keys
|
||||
val a = AttributeKey[Int]("a")
|
||||
val b = AttributeKey[Int]("b")
|
||||
|
||||
// Scope these keys
|
||||
val a3 = ScopedKey(Scope(3), a)
|
||||
val a4 = ScopedKey(Scope(4), a)
|
||||
val a5 = ScopedKey(Scope(5), a)
|
||||
|
||||
val b4 = ScopedKey(Scope(4), b)
|
||||
|
||||
// Define some settings
|
||||
val mySettings: Seq[Setting[_]] = Seq(
|
||||
setting(a3, value(3)),
|
||||
setting(b4, map(a4)(_ * 3)),
|
||||
update(a5)(_ + 1)
|
||||
)
|
||||
|
||||
// "compiles" and applies the settings.
|
||||
// This can be split into multiple steps to access intermediate results if desired.
|
||||
// The 'inspect' command operates on the output of 'compile', for example.
|
||||
val applied: Settings[Scope] =
|
||||
makeWithCompiledMap(mySettings)(delegates, scopeLocal, showFullKey)._2
|
||||
|
||||
// Show results.
|
||||
/* for(i <- 0 to 5; k <- Seq(a, b)) {
|
||||
println( k.label + i + " = " + applied.get( Scope(i), k) )
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Output:
|
||||
* For the None results, we never defined the value and there was no value to delegate to.
|
||||
* For a3, we explicitly defined it to be 3.
|
||||
* a4 wasn't defined, so it delegates to a3 according to our delegates function.
|
||||
* b4 gets the value for a4 (which delegates to a3, so it is 3) and multiplies by 3
|
||||
* a5 is defined as the previous value of a5 + 1 and
|
||||
* since no previous value of a5 was defined, it delegates to a4, resulting in 3+1=4.
|
||||
* b5 isn't defined explicitly, so it delegates to b4 and is therefore equal to 9 as well
|
||||
* a0 = None
|
||||
* b0 = None
|
||||
* a1 = None
|
||||
* b1 = None
|
||||
* a2 = None
|
||||
* b2 = None
|
||||
* a3 = Some(3)
|
||||
* b3 = None
|
||||
* a4 = Some(3)
|
||||
* b4 = Some(9)
|
||||
* a5 = Some(4)
|
||||
* b5 = Some(9)
|
||||
*/
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import org.scalacheck._, Prop._
|
||||
|
||||
object SettingsTest extends Properties("settings") {
|
||||
val settingsExample: SettingsExample = SettingsExample()
|
||||
import settingsExample._
|
||||
val settingsUsage = SettingsUsage(settingsExample)
|
||||
import settingsUsage._
|
||||
|
||||
import scala.reflect.Manifest
|
||||
|
||||
final val ChainMax = 5000
|
||||
lazy val chainLengthGen = Gen.choose(1, ChainMax)
|
||||
|
||||
property("Basic settings test") = secure(all(tests: _*))
|
||||
|
||||
property("Basic chain") = forAll(chainLengthGen) { (i: Int) =>
|
||||
val abs = math.abs(i)
|
||||
singleIntTest(chain(abs, value(0)), abs)
|
||||
}
|
||||
property("Basic bind chain") = forAll(chainLengthGen) { (i: Int) =>
|
||||
val abs = math.abs(i)
|
||||
singleIntTest(chainBind(value(abs)), 0)
|
||||
}
|
||||
|
||||
property("Allows references to completed settings") = forAllNoShrink(30) { allowedReference }
|
||||
final def allowedReference(intermediate: Int): Prop = {
|
||||
val top = value(intermediate)
|
||||
def iterate(init: Initialize[Int]): Initialize[Int] =
|
||||
bind(init) { t =>
|
||||
if (t <= 0)
|
||||
top
|
||||
else
|
||||
iterate(value(t - 1))
|
||||
}
|
||||
evaluate(setting(chk, iterate(top)) :: Nil); true
|
||||
}
|
||||
|
||||
property("Derived setting chain depending on (prev derived, normal setting)") =
|
||||
forAllNoShrink(Gen.choose(1, 100).label("numSettings")) { derivedSettings }
|
||||
final def derivedSettings(nr: Int): Prop = {
|
||||
val genScopedKeys = {
|
||||
// We wan
|
||||
// t to generate lists of keys that DO NOT inclue the "ch" key we use to check things.
|
||||
val attrKeys = mkAttrKeys[Int](nr).filter(_.forall(_.label != "ch"))
|
||||
attrKeys map (_ map (ak => ScopedKey(Scope(0), ak)))
|
||||
}.label("scopedKeys").filter(_.nonEmpty)
|
||||
forAll(genScopedKeys) { scopedKeys =>
|
||||
try {
|
||||
// Note; It's evil to grab last IF you haven't verified the set can't be empty.
|
||||
val last = scopedKeys.last
|
||||
val derivedSettings: Seq[Setting[Int]] = (
|
||||
for {
|
||||
List(scoped0, scoped1) <- chk :: scopedKeys sliding 2
|
||||
nextInit = if (scoped0 == chk) chk
|
||||
else
|
||||
(scoped0 zipWith chk) { (p, _) =>
|
||||
p + 1
|
||||
}
|
||||
} yield derive(setting(scoped1, nextInit))
|
||||
).toSeq
|
||||
|
||||
{
|
||||
// Note: This causes a cycle refernec error, quite frequently.
|
||||
checkKey(last, Some(nr - 1), evaluate(setting(chk, value(0)) +: derivedSettings)) :| "Not derived?"
|
||||
} && {
|
||||
checkKey(last, None, evaluate(derivedSettings)) :| "Should not be derived"
|
||||
}
|
||||
} catch {
|
||||
case t: Throwable =>
|
||||
// TODO - For debugging only.
|
||||
t.printStackTrace(System.err)
|
||||
throw t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def mkAttrKeys[T](nr: Int)(implicit mf: Manifest[T]): Gen[List[AttributeKey[T]]] = {
|
||||
import Gen._
|
||||
val nonEmptyAlphaStr =
|
||||
nonEmptyListOf(alphaChar)
|
||||
.map({ xs: List[Char] =>
|
||||
val s = xs.mkString
|
||||
s.take(1).toLowerCase + s.drop(1)
|
||||
})
|
||||
.suchThat(_.forall(_.isLetter))
|
||||
|
||||
(for {
|
||||
list <- Gen.listOfN(nr, nonEmptyAlphaStr) suchThat (l => l.size == l.distinct.size)
|
||||
item <- list
|
||||
} yield AttributeKey[T](item)).label(s"mkAttrKeys($nr)")
|
||||
}
|
||||
|
||||
property("Derived setting(s) replace DerivedSetting in the Seq[Setting[_]]") =
|
||||
derivedKeepsPosition
|
||||
final def derivedKeepsPosition: Prop = {
|
||||
val a: ScopedKey[Int] = ScopedKey(Scope(0), AttributeKey[Int]("a"))
|
||||
val b: ScopedKey[Int] = ScopedKey(Scope(0), AttributeKey[Int]("b"))
|
||||
val prop1 = {
|
||||
val settings: Seq[Setting[_]] = Seq(
|
||||
setting(a, value(3)),
|
||||
setting(b, value(6)),
|
||||
derive(setting(b, a)),
|
||||
setting(a, value(5)),
|
||||
setting(b, value(8))
|
||||
)
|
||||
val ev = evaluate(settings)
|
||||
checkKey(a, Some(5), ev) && checkKey(b, Some(8), ev)
|
||||
}
|
||||
val prop2 = {
|
||||
val settings: Seq[Setting[Int]] = Seq(
|
||||
setting(a, value(3)),
|
||||
setting(b, value(6)),
|
||||
derive(setting(b, a)),
|
||||
setting(a, value(5))
|
||||
)
|
||||
val ev = evaluate(settings)
|
||||
checkKey(a, Some(5), ev) && checkKey(b, Some(5), ev)
|
||||
}
|
||||
prop1 && prop2
|
||||
}
|
||||
|
||||
property(
|
||||
"DerivedSetting in ThisBuild scopes derived settings under projects thus allowing safe +="
|
||||
) = forAllNoShrink(Gen.choose(1, 100)) { derivedSettingsScope }
|
||||
final def derivedSettingsScope(nrProjects: Int): Prop = {
|
||||
forAll(mkAttrKeys[Int](2)) {
|
||||
case List(key, derivedKey) =>
|
||||
val projectKeys = for { proj <- 1 to nrProjects } yield ScopedKey(Scope(1, proj), key)
|
||||
val projectDerivedKeys = for { proj <- 1 to nrProjects } yield ScopedKey(
|
||||
Scope(1, proj),
|
||||
derivedKey
|
||||
)
|
||||
val globalKey = ScopedKey(Scope(0), key)
|
||||
val globalDerivedKey = ScopedKey(Scope(0), derivedKey)
|
||||
// Each project defines an initial value, but the update is defined in globalKey.
|
||||
// However, the derived Settings that come from this should be scoped in each project.
|
||||
val settings: Seq[Setting[_]] =
|
||||
derive(setting(globalDerivedKey, settingsExample.map(globalKey)(_ + 1))) +: projectKeys
|
||||
.map(pk => setting(pk, value(0)))
|
||||
val ev = evaluate(settings)
|
||||
// Also check that the key has no value at the "global" scope
|
||||
val props = for { pk <- projectDerivedKeys } yield checkKey(pk, Some(1), ev)
|
||||
checkKey(globalDerivedKey, None, ev) && Prop.all(props: _*)
|
||||
}
|
||||
}
|
||||
|
||||
// Circular (dynamic) references currently loop infinitely.
|
||||
// This is the expected behavior (detecting dynamic cycles is expensive),
|
||||
// but it may be necessary to provide an option to detect them (with a performance hit)
|
||||
// This would test that cycle detection.
|
||||
// property("Catches circular references") = forAll(chainLengthGen) { checkCircularReferences _ }
|
||||
final def checkCircularReferences(intermediate: Int): Prop = {
|
||||
val ccr = new CCR(intermediate)
|
||||
try {
|
||||
evaluate(setting(chk, ccr.top) :: Nil); false
|
||||
} catch {
|
||||
case _: java.lang.Exception => true
|
||||
}
|
||||
}
|
||||
|
||||
def tests =
|
||||
for (i <- 0 to 5; k <- Seq(a, b)) yield {
|
||||
val expected = expectedValues(2 * i + (if (k == a) 0 else 1))
|
||||
checkKey[Int](ScopedKey(Scope(i), k), expected, applied)
|
||||
}
|
||||
|
||||
lazy val expectedValues = None :: None :: None :: None :: None :: None :: Some(3) :: None ::
|
||||
Some(3) :: Some(9) :: Some(4) :: Some(9) :: Nil
|
||||
|
||||
lazy val ch = AttributeKey[Int]("ch")
|
||||
lazy val chk = ScopedKey(Scope(0), ch)
|
||||
def chain(i: Int, prev: Initialize[Int]): Initialize[Int] =
|
||||
if (i <= 0) prev else chain(i - 1, prev(_ + 1))
|
||||
|
||||
def chainBind(prev: Initialize[Int]): Initialize[Int] =
|
||||
bind(prev) { v =>
|
||||
if (v <= 0) prev else chainBind(value(v - 1))
|
||||
}
|
||||
def singleIntTest(i: Initialize[Int], expected: Int) = {
|
||||
val eval = evaluate(setting(chk, i) :: Nil)
|
||||
checkKey(chk, Some(expected), eval)
|
||||
}
|
||||
|
||||
def checkKey[T](key: ScopedKey[T], expected: Option[T], settings: Settings[Scope]) = {
|
||||
val value = settings.get(key.scope, key.key)
|
||||
("Key: " + key) |:
|
||||
("Value: " + value) |:
|
||||
("Expected: " + expected) |:
|
||||
(value == expected)
|
||||
}
|
||||
|
||||
def evaluate(settings: Seq[Setting[_]]): Settings[Scope] =
|
||||
try {
|
||||
makeWithCompiledMap(settings)(delegates, scopeLocal, showFullKey)._2
|
||||
} catch {
|
||||
case e: Throwable => e.printStackTrace(); throw e
|
||||
}
|
||||
}
|
||||
// This setup is a workaround for module synchronization issues
|
||||
final class CCR(intermediate: Int) {
|
||||
import SettingsTest.settingsExample._
|
||||
lazy val top = iterate(value(intermediate))
|
||||
def iterate(init: Initialize[Int]): Initialize[Int] =
|
||||
bind(init) { t =>
|
||||
if (t <= 0)
|
||||
top
|
||||
else
|
||||
iterate(value(t - 1))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
abstract class UnitSpec extends FlatSpec with Matchers
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
sbt: Completion Component
|
||||
Copyright 2011 - 2017, Lightbend, Inc.
|
||||
Copyright 2008 - 2010, Mark Harrah
|
||||
Licensed under BSD-3-Clause license (see LICENSE)
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io._
|
||||
import java.util.{ List => JList }
|
||||
|
||||
import jline.console.ConsoleReader
|
||||
import jline.console.history.{ FileHistory, MemoryHistory }
|
||||
import org.jline.reader.{
|
||||
Candidate,
|
||||
Completer,
|
||||
EndOfFileException,
|
||||
LineReader => JLineReader,
|
||||
LineReaderBuilder,
|
||||
ParsedLine,
|
||||
UserInterruptException,
|
||||
}
|
||||
import org.jline.utils.ClosedException
|
||||
import sbt.internal.util.complete.Parser
|
||||
import sbt.io.syntax._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.control.NonFatal
|
||||
import java.nio.channels.ClosedByInterruptException
|
||||
import java.net.MalformedURLException
|
||||
|
||||
import org.jline.builtins.InputRC
|
||||
|
||||
trait LineReader extends AutoCloseable {
|
||||
def readLine(prompt: String, mask: Option[Char] = None): Option[String]
|
||||
override def close(): Unit = {}
|
||||
}
|
||||
|
||||
object LineReader {
|
||||
val HandleCONT =
|
||||
!java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
|
||||
val MaxHistorySize = 500
|
||||
|
||||
private def completer(parser: Parser[_]): Completer = new Completer {
|
||||
def complete(lr: JLineReader, pl: ParsedLine, candidates: JList[Candidate]): Unit = {
|
||||
Parser.completions(parser, pl.line(), 10).get.foreach { c =>
|
||||
/*
|
||||
* For commands like `~` that delegate parsing to another parser, the `~` may be
|
||||
* excluded from the completion result. For example,
|
||||
* ~testOnly <TAB>
|
||||
* might return results like
|
||||
* 'testOnly ;'
|
||||
* 'testOnly com.foo.FooSpec'
|
||||
* ...
|
||||
* If we use the raw display, JLine will reject the completions because they are
|
||||
* missing the leading `~`. To workaround this, we append to the result to the
|
||||
* line provided the line does not end with " ". This fixes the missing `~` in
|
||||
* the prefix problem. We also need to split the line on space and take the
|
||||
* last token and append to that otherwise the completion will double print
|
||||
* the prefix, so that `testOnly com<Tab>` might expand to something like:
|
||||
* `testOnly testOnly\ com.foo.FooSpec` instead of `testOnly com.foo.FooSpec`.
|
||||
*/
|
||||
if (c.append.nonEmpty) {
|
||||
val cand = pl.line() match {
|
||||
case line if line.endsWith(" ") => c.append
|
||||
case line => line.split(" ").last + c.append
|
||||
}
|
||||
// https://github.com/jline/jline3/blob/9a4971868e4bdd29a36e454de01f54d3cd6071e0/reader/src/main/java/org/jline/reader/Candidate.java#L123-L131
|
||||
// "If the candidate is complete and is selected, a space separator will be added."
|
||||
val complete = false
|
||||
candidates.add(new Candidate(cand, cand, null, null, null, null, complete))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private[this] def inputrcFileUrl(): Option[URL] = {
|
||||
// keep jline2 compatibility
|
||||
// https://github.com/jline/jline2/blob/12b98d94589e3bd6a6/src/main/java/jline/console/ConsoleReader.java#L291-L306
|
||||
sys.props
|
||||
.get("jline.inputrc")
|
||||
.flatMap { path =>
|
||||
try {
|
||||
Some(url(path))
|
||||
} catch {
|
||||
case _: MalformedURLException =>
|
||||
Some(file(path).toURI.toURL)
|
||||
}
|
||||
}
|
||||
.orElse {
|
||||
sys.props.get("user.home").map { home =>
|
||||
val f = file(home) / ".inputrc"
|
||||
(if (f.isFile) f else file("/etc/inputrc")).toURI.toURL
|
||||
}
|
||||
}
|
||||
}
|
||||
// cache on memory.
|
||||
private[this] lazy val inputrcFileContents: Option[Array[Byte]] =
|
||||
inputrcFileUrl().map(in => sbt.io.IO.readBytes(in.openStream()))
|
||||
def createReader(
|
||||
historyPath: Option[File],
|
||||
parser: Parser[_],
|
||||
terminal: Terminal,
|
||||
): LineReader = {
|
||||
// We may want to consider insourcing LineReader.java from jline. We don't otherwise
|
||||
// directly need jline3 for sbt.
|
||||
new LineReader {
|
||||
override def readLine(prompt: String, mask: Option[Char]): Option[String] = {
|
||||
val term = JLine3(terminal)
|
||||
val reader = LineReaderBuilder.builder().terminal(term).completer(completer(parser)).build()
|
||||
try {
|
||||
inputrcFileContents.foreach { bytes =>
|
||||
InputRC.configure(
|
||||
reader,
|
||||
new ByteArrayInputStream(bytes)
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
case NonFatal(_) =>
|
||||
// ignore
|
||||
}
|
||||
historyPath.foreach(f => reader.setVariable(JLineReader.HISTORY_FILE, f))
|
||||
val signalRegistration = terminal match {
|
||||
case _: Terminal.ConsoleTerminal => Some(Signals.register(() => terminal.write(-1)))
|
||||
case _ => None
|
||||
}
|
||||
try terminal.withRawInput {
|
||||
Option(mask.map(reader.readLine(prompt, _)).getOrElse(reader.readLine(prompt)))
|
||||
} catch {
|
||||
case e: EndOfFileException =>
|
||||
if (terminal == Terminal.console && System.console == null) None
|
||||
else Some("exit")
|
||||
case _: IOError | _: ClosedException => Some("exit")
|
||||
case _: UserInterruptException | _: ClosedByInterruptException |
|
||||
_: UncheckedIOException =>
|
||||
throw new InterruptedException
|
||||
} finally {
|
||||
signalRegistration.foreach(_.remove())
|
||||
terminal.prompt.reset()
|
||||
term.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createJLine2Reader(
|
||||
historyPath: Option[File],
|
||||
terminal: Terminal,
|
||||
prompt: Prompt = Prompt.Running,
|
||||
): ConsoleReader = {
|
||||
val cr = Terminal.createReader(terminal, prompt)
|
||||
cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650
|
||||
cr.setBellEnabled(false)
|
||||
val h = historyPath match {
|
||||
case None => new MemoryHistory
|
||||
case Some(file) => new FileHistory(file): MemoryHistory
|
||||
}
|
||||
h.setMaxSize(MaxHistorySize)
|
||||
cr.setHistory(h)
|
||||
cr.setHistoryEnabled(true)
|
||||
cr
|
||||
}
|
||||
def simple(terminal: Terminal): LineReader = new SimpleReader(None, HandleCONT, terminal)
|
||||
def simple(
|
||||
historyPath: Option[File],
|
||||
handleCONT: Boolean = HandleCONT,
|
||||
injectThreadSleep: Boolean = false
|
||||
): LineReader = new SimpleReader(historyPath, handleCONT, injectThreadSleep)
|
||||
}
|
||||
|
||||
abstract class JLine extends LineReader {
|
||||
protected[this] def handleCONT: Boolean
|
||||
protected[this] def reader: ConsoleReader
|
||||
@deprecated("For binary compatibility only", "1.4.0")
|
||||
protected[this] def injectThreadSleep: Boolean = false
|
||||
@deprecated("For binary compatibility only", "1.4.0")
|
||||
protected[this] lazy val in: InputStream = Terminal.wrappedSystemIn
|
||||
|
||||
override def readLine(prompt: String, mask: Option[Char] = None): Option[String] =
|
||||
try {
|
||||
unsynchronizedReadLine(prompt, mask)
|
||||
} catch {
|
||||
case _: InterruptedException =>
|
||||
// println("readLine: InterruptedException")
|
||||
Option("")
|
||||
}
|
||||
|
||||
private[this] def unsynchronizedReadLine(prompt: String, mask: Option[Char]): Option[String] =
|
||||
readLineWithHistory(prompt, mask) map { x =>
|
||||
x.trim
|
||||
}
|
||||
|
||||
private[this] def readLineWithHistory(prompt: String, mask: Option[Char]): Option[String] =
|
||||
reader.getHistory match {
|
||||
case fh: FileHistory =>
|
||||
try readLineDirect(prompt, mask)
|
||||
finally fh.flush()
|
||||
case _ => readLineDirect(prompt, mask)
|
||||
}
|
||||
|
||||
private[this] def readLineDirect(prompt: String, mask: Option[Char]): Option[String] =
|
||||
if (handleCONT)
|
||||
Signals.withHandler(() => resume(), signal = Signals.CONT)(
|
||||
() => readLineDirectRaw(prompt, mask)
|
||||
)
|
||||
else
|
||||
readLineDirectRaw(prompt, mask)
|
||||
|
||||
private[this] def readLineDirectRaw(prompt: String, mask: Option[Char]): Option[String] = {
|
||||
val newprompt = handleMultilinePrompt(prompt)
|
||||
mask match {
|
||||
case Some(m) => Option(reader.readLine(newprompt, m))
|
||||
case None => Option(reader.readLine(newprompt))
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def handleMultilinePrompt(prompt: String): String = {
|
||||
val lines0 = """\r?\n""".r.split(prompt)
|
||||
lines0.length match {
|
||||
case 0 | 1 => handleProgress(prompt)
|
||||
case _ =>
|
||||
val lines = lines0.toList map handleProgress
|
||||
// Workaround for regression jline/jline2#205
|
||||
reader.getOutput.write(lines.init.mkString("\n") + "\n")
|
||||
lines.last
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def handleProgress(prompt: String): String = {
|
||||
import ConsoleAppender._
|
||||
if (showProgress) s"$DeleteLine" + prompt
|
||||
else prompt
|
||||
}
|
||||
|
||||
private[this] def resume(): Unit = {
|
||||
Terminal.reset()
|
||||
reader.drawLine()
|
||||
reader.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@deprecated("Use LineReader apis", "1.4.0")
|
||||
private[sbt] object JLine {
|
||||
@deprecated("For binary compatibility only", "1.4.0")
|
||||
protected[this] val originalIn = new FileInputStream(FileDescriptor.in)
|
||||
|
||||
@deprecated("Handled by Terminal.fixTerminalProperty", "1.4.0")
|
||||
private[sbt] def fixTerminalProperty(): Unit = ()
|
||||
|
||||
@deprecated("For binary compatibility only", "1.4.0")
|
||||
private[sbt] def makeInputStream(injectThreadSleep: Boolean): InputStream =
|
||||
if (injectThreadSleep) new InputStreamWrapper(originalIn, 2.milliseconds)
|
||||
else originalIn
|
||||
|
||||
// When calling this, ensure that enableEcho has been or will be called.
|
||||
// TerminalFactory.get will initialize the terminal to disable echo.
|
||||
@deprecated("Don't use jline.Terminal directly", "1.4.0")
|
||||
private[sbt] def terminal: jline.Terminal = Terminal.deprecatedTeminal
|
||||
|
||||
/**
|
||||
* For accessing the JLine Terminal object.
|
||||
* This ensures synchronized access as well as re-enabling echo after getting the Terminal.
|
||||
*/
|
||||
@deprecated(
|
||||
"Don't use jline.Terminal directly. Use Terminal.get.withCanonicalIn instead.",
|
||||
"1.4.0"
|
||||
)
|
||||
def usingTerminal[T](f: jline.Terminal => T): T = f(Terminal.get.toJLine)
|
||||
|
||||
@deprecated("unused", "1.4.0")
|
||||
def createReader(): ConsoleReader = createReader(None, Terminal.wrappedSystemIn)
|
||||
|
||||
@deprecated("Use LineReader.createReader", "1.4.0")
|
||||
def createReader(historyPath: Option[File], in: InputStream): ConsoleReader = {
|
||||
val cr = Terminal.createReader(Terminal.console, Prompt.Running)
|
||||
cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650
|
||||
cr.setBellEnabled(false)
|
||||
val h = historyPath match {
|
||||
case None => new MemoryHistory
|
||||
case Some(file) => new FileHistory(file): MemoryHistory
|
||||
}
|
||||
h.setMaxSize(MaxHistorySize)
|
||||
cr.setHistory(h)
|
||||
cr
|
||||
}
|
||||
|
||||
@deprecated("Avoid referencing JLine directly.", "1.4.0")
|
||||
def withJLine[T](action: => T): T = Terminal.get.withRawInput(action)
|
||||
|
||||
@deprecated("Use LineReader.simple instead", "1.4.0")
|
||||
def simple(
|
||||
historyPath: Option[File],
|
||||
handleCONT: Boolean = LineReader.HandleCONT,
|
||||
injectThreadSleep: Boolean = false
|
||||
): SimpleReader = new SimpleReader(historyPath, handleCONT, injectThreadSleep)
|
||||
|
||||
@deprecated("Use LineReader.MaxHistorySize", "1.4.0")
|
||||
val MaxHistorySize = LineReader.MaxHistorySize
|
||||
|
||||
@deprecated("Use LineReader.HandleCONT", "1.4.0")
|
||||
val HandleCONT = LineReader.HandleCONT
|
||||
}
|
||||
|
||||
@deprecated("For binary compatibility only", "1.4.0")
|
||||
private[sbt] class InputStreamWrapper(is: InputStream, val poll: Duration)
|
||||
extends FilterInputStream(is) {
|
||||
@tailrec final override def read(): Int =
|
||||
if (is.available() != 0) is.read()
|
||||
else {
|
||||
Thread.sleep(poll.toMillis)
|
||||
read()
|
||||
}
|
||||
|
||||
@tailrec final override def read(b: Array[Byte]): Int =
|
||||
if (is.available() != 0) is.read(b)
|
||||
else {
|
||||
Thread.sleep(poll.toMillis)
|
||||
read(b)
|
||||
}
|
||||
|
||||
@tailrec final override def read(b: Array[Byte], off: Int, len: Int): Int =
|
||||
if (is.available() != 0) is.read(b, off, len)
|
||||
else {
|
||||
Thread.sleep(poll.toMillis)
|
||||
read(b, off, len)
|
||||
}
|
||||
}
|
||||
|
||||
final class FullReader(
|
||||
historyPath: Option[File],
|
||||
complete: Parser[_],
|
||||
val handleCONT: Boolean,
|
||||
terminal: Terminal
|
||||
) extends JLine {
|
||||
@deprecated("Use the constructor with no injectThreadSleep parameter", "1.4.0")
|
||||
def this(
|
||||
historyPath: Option[File],
|
||||
complete: Parser[_],
|
||||
handleCONT: Boolean = LineReader.HandleCONT,
|
||||
injectThreadSleep: Boolean = false
|
||||
) =
|
||||
this(
|
||||
historyPath,
|
||||
complete,
|
||||
handleCONT,
|
||||
Terminal.console
|
||||
)
|
||||
protected[this] val reader: ConsoleReader = {
|
||||
val cr = LineReader.createJLine2Reader(historyPath, terminal)
|
||||
sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete)
|
||||
cr
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleReader private[sbt] (
|
||||
historyPath: Option[File],
|
||||
val handleCONT: Boolean,
|
||||
terminal: Terminal
|
||||
) extends JLine {
|
||||
def this(historyPath: Option[File], handleCONT: Boolean, injectThreadSleep: Boolean) =
|
||||
this(historyPath, handleCONT, Terminal.console)
|
||||
protected[this] lazy val reader: ConsoleReader =
|
||||
LineReader.createJLine2Reader(historyPath, terminal)
|
||||
}
|
||||
|
||||
object SimpleReader extends SimpleReader(None, LineReader.HandleCONT, false) {
|
||||
def apply(terminal: Terminal): SimpleReader =
|
||||
new SimpleReader(None, LineReader.HandleCONT, terminal)
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
/**
|
||||
* Represents a set of completions.
|
||||
* It exists instead of implicitly defined operations on top of Set[Completion]
|
||||
* for laziness.
|
||||
*/
|
||||
sealed trait Completions {
|
||||
def get: Set[Completion]
|
||||
|
||||
final def x(o: Completions): Completions = flatMap(_ x o)
|
||||
final def ++(o: Completions): Completions = Completions(get ++ o.get)
|
||||
final def +:(o: Completion): Completions = Completions(get + o)
|
||||
final def filter(f: Completion => Boolean): Completions = Completions(get filter f)
|
||||
final def filterS(f: String => Boolean): Completions = filter(c => f(c.append))
|
||||
|
||||
override def toString = get.mkString("Completions(", ",", ")")
|
||||
|
||||
final def flatMap(f: Completion => Completions): Completions =
|
||||
Completions(get.flatMap(c => f(c).get))
|
||||
|
||||
final def map(f: Completion => Completion): Completions = Completions(get map f)
|
||||
|
||||
override final def hashCode = get.hashCode
|
||||
override final def equals(o: Any) = o match {
|
||||
case c: Completions => get == c.get; case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
object Completions {
|
||||
|
||||
/** Returns a lazy Completions instance using the provided Completion Set. */
|
||||
def apply(cs: => Set[Completion]): Completions = new Completions {
|
||||
lazy val get = cs
|
||||
}
|
||||
|
||||
/** Returns a strict Completions instance using the provided Completion Set. */
|
||||
def strict(cs: Set[Completion]): Completions = apply(cs)
|
||||
|
||||
/**
|
||||
* No suggested completions, not even the empty Completion.
|
||||
* This typically represents invalid input.
|
||||
*/
|
||||
val nil: Completions = strict(Set.empty)
|
||||
|
||||
/**
|
||||
* Only includes an empty Suggestion.
|
||||
* This typically represents valid input that either has no completions or accepts no further input.
|
||||
*/
|
||||
val empty: Completions = strict(Set.empty + Completion.empty)
|
||||
|
||||
/** Returns a strict Completions instance containing only the provided Completion.*/
|
||||
def single(c: Completion): Completions = strict(Set.empty + c)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a completion.
|
||||
* The abstract members `display` and `append` are best explained with an example.
|
||||
*
|
||||
* Assuming space-delimited tokens, processing this:
|
||||
* am is are w<TAB>
|
||||
* could produce these Completions:
|
||||
* Completion { display = "was"; append = "as" }
|
||||
* Completion { display = "were"; append = "ere" }
|
||||
* to suggest the tokens "was" and "were".
|
||||
*
|
||||
* In this way, two pieces of information are preserved:
|
||||
* 1) what needs to be appended to the current input if a completion is selected
|
||||
* 2) the full token being completed, which is useful for presenting a user with choices to select
|
||||
*/
|
||||
sealed trait Completion {
|
||||
|
||||
/** The proposed suffix to append to the existing input to complete the last token in the input.*/
|
||||
def append: String
|
||||
|
||||
/** The string to present to the user to represent the full token being suggested.*/
|
||||
def display: String
|
||||
|
||||
/** True if this Completion is suggesting the empty string.*/
|
||||
def isEmpty: Boolean
|
||||
|
||||
/** Appends the completions in `o` with the completions in this Completion.*/
|
||||
def ++(o: Completion): Completion = Completion.concat(this, o)
|
||||
|
||||
final def x(o: Completions): Completions =
|
||||
if (Completion evaluatesRight this) o.map(this ++ _) else Completions.strict(Set.empty + this)
|
||||
|
||||
override final lazy val hashCode = Completion.hashCode(this)
|
||||
override final def equals(o: Any) = o match {
|
||||
case c: Completion => Completion.equal(this, c); case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
final class DisplayOnly(val display: String) extends Completion {
|
||||
def isEmpty = display.isEmpty
|
||||
def append = ""
|
||||
override def toString = "{" + display + "}"
|
||||
}
|
||||
|
||||
final class Token(val display: String, val append: String) extends Completion {
|
||||
def isEmpty = display.isEmpty && append.isEmpty
|
||||
override final def toString = "[" + display + "]++" + append
|
||||
}
|
||||
|
||||
final class Suggestion(val append: String) extends Completion {
|
||||
def isEmpty = append.isEmpty
|
||||
def display = append
|
||||
override def toString = append
|
||||
}
|
||||
|
||||
object Completion {
|
||||
def concat(a: Completion, b: Completion): Completion =
|
||||
(a, b) match {
|
||||
case (as: Suggestion, bs: Suggestion) => suggestion(as.append + bs.append)
|
||||
case (at: Token, _) if at.append.isEmpty => b
|
||||
case _ if a.isEmpty => b
|
||||
case _ => a
|
||||
}
|
||||
|
||||
def evaluatesRight(a: Completion): Boolean =
|
||||
a match {
|
||||
case _: Suggestion => true
|
||||
case at: Token if at.append.isEmpty => true
|
||||
case _ => a.isEmpty
|
||||
}
|
||||
|
||||
def equal(a: Completion, b: Completion): Boolean =
|
||||
(a, b) match {
|
||||
case (as: Suggestion, bs: Suggestion) => as.append == bs.append
|
||||
case (ad: DisplayOnly, bd: DisplayOnly) => ad.display == bd.display
|
||||
case (at: Token, bt: Token) => at.display == bt.display && at.append == bt.append
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def hashCode(a: Completion): Int =
|
||||
a match {
|
||||
case as: Suggestion => (0, as.append).hashCode
|
||||
case ad: DisplayOnly => (1, ad.display).hashCode
|
||||
case at: Token => (2, at.display, at.append).hashCode
|
||||
}
|
||||
|
||||
val empty: Completion = suggestion("")
|
||||
def single(c: Char): Completion = suggestion(c.toString)
|
||||
|
||||
def displayOnly(value: String): Completion = new DisplayOnly(value)
|
||||
|
||||
def token(prepend: String, append: String): Completion =
|
||||
new Token(prepend + append, append)
|
||||
|
||||
/** @since 0.12.1 */
|
||||
def tokenDisplay(append: String, display: String): Completion = new Token(display, append)
|
||||
|
||||
def suggestion(value: String): Completion = new Suggestion(value)
|
||||
|
||||
@deprecated("No longer used. for binary compatibility", "1.1.0")
|
||||
private[complete] def displayOnly(value: => String): Completion = new DisplayOnly(value)
|
||||
|
||||
@deprecated("No longer used. for binary compatibility", "1.1.0")
|
||||
private[complete] def token(prepend: => String, append: => String): Completion =
|
||||
new Token(prepend + append, append)
|
||||
|
||||
@deprecated("No longer used. for binary compatibility", "1.1.0")
|
||||
private[complete] def suggestion(value: => String): Completion = new Suggestion(value)
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import java.lang.Character.{ toLowerCase => lower }
|
||||
|
||||
/** @author Paul Phillips */
|
||||
object EditDistance {
|
||||
|
||||
/**
|
||||
* Translated from the java version at
|
||||
* http://www.merriampark.com/ld.htm
|
||||
* which is declared to be public domain.
|
||||
*/
|
||||
def levenshtein(
|
||||
s: String,
|
||||
t: String,
|
||||
insertCost: Int = 1,
|
||||
deleteCost: Int = 1,
|
||||
subCost: Int = 1,
|
||||
transposeCost: Int = 1,
|
||||
matchCost: Int = 0,
|
||||
caseCost: Int = 1,
|
||||
transpositions: Boolean = false
|
||||
): Int = {
|
||||
val _ = transposeCost
|
||||
val n = s.length
|
||||
val m = t.length
|
||||
if (n == 0) return m
|
||||
if (m == 0) return n
|
||||
|
||||
val d = Array.ofDim[Int](n + 1, m + 1)
|
||||
0 to n foreach (x => d(x)(0) = x)
|
||||
0 to m foreach (x => d(0)(x) = x)
|
||||
|
||||
for (i <- 1 to n; s_i = s(i - 1); j <- 1 to m) {
|
||||
val t_j = t(j - 1)
|
||||
val cost = if (s_i == t_j) matchCost else if (lower(s_i) == lower(t_j)) caseCost else subCost
|
||||
|
||||
val c1 = d(i - 1)(j) + deleteCost
|
||||
val c2 = d(i)(j - 1) + insertCost
|
||||
val c3 = d(i - 1)(j - 1) + cost
|
||||
|
||||
d(i)(j) = c1 min c2 min c3
|
||||
|
||||
if (transpositions) {
|
||||
if (i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1))
|
||||
d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost)
|
||||
}
|
||||
}
|
||||
|
||||
d(n)(m)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import java.io.File
|
||||
import sbt.io.IO
|
||||
|
||||
/**
|
||||
* These sources of examples are used in parsers for user input completion. An example of such a source is the
|
||||
* [[sbt.internal.util.complete.FileExamples]] class, which provides a list of suggested files to the user as they press the
|
||||
* TAB key in the console.
|
||||
*/
|
||||
trait ExampleSource {
|
||||
|
||||
/**
|
||||
* @return a (possibly lazy) list of completion example strings. These strings are continuations of user's input. The
|
||||
* user's input is incremented with calls to [[withAddedPrefix]].
|
||||
*/
|
||||
def apply(): Iterable[String]
|
||||
|
||||
/**
|
||||
* @param addedPrefix a string that just typed in by the user.
|
||||
* @return a new source of only those examples that start with the string typed by the user so far (with addition of
|
||||
* the just added prefix).
|
||||
*/
|
||||
def withAddedPrefix(addedPrefix: String): ExampleSource
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience example source that wraps any collection of strings into a source of examples.
|
||||
* @param examples the examples that will be displayed to the user when they press the TAB key.
|
||||
*/
|
||||
sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSource {
|
||||
override def withAddedPrefix(addedPrefix: String): ExampleSource =
|
||||
FixedSetExamples(examplesWithRemovedPrefix(addedPrefix))
|
||||
|
||||
override def apply(): Iterable[String] = examples
|
||||
|
||||
private def examplesWithRemovedPrefix(prefix: String) = examples.collect {
|
||||
case example if example startsWith prefix => example substring prefix.length
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides path completion examples based on files in the base directory.
|
||||
* @param base the directory within which this class will search for completion examples.
|
||||
* @param prefix the part of the path already written by the user.
|
||||
*/
|
||||
class FileExamples(base: File, prefix: String = "") extends ExampleSource {
|
||||
override def apply(): Stream[String] = files(base).map(_ substring prefix.length)
|
||||
|
||||
override def withAddedPrefix(addedPrefix: String): FileExamples =
|
||||
new FileExamples(base, prefix + addedPrefix)
|
||||
|
||||
protected def files(directory: File): Stream[String] = {
|
||||
val childPaths = IO.listFiles(directory).toStream
|
||||
val prefixedDirectChildPaths = childPaths map { IO.relativize(base, _).get } filter {
|
||||
_ startsWith prefix
|
||||
}
|
||||
val dirsToRecurseInto = childPaths filter { _.isDirectory } map { IO.relativize(base, _).get } filter {
|
||||
dirStartsWithPrefix
|
||||
}
|
||||
prefixedDirectChildPaths append dirsToRecurseInto.flatMap(dir => files(new File(base, dir)))
|
||||
}
|
||||
|
||||
private def dirStartsWithPrefix(relativizedPath: String): Boolean =
|
||||
(relativizedPath startsWith prefix) || (prefix startsWith relativizedPath)
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import History.number
|
||||
import java.io.File
|
||||
|
||||
final class History private (val lines: IndexedSeq[String], val path: Option[File]) {
|
||||
private def reversed = lines.reverse
|
||||
|
||||
def all: Seq[String] = lines
|
||||
def size = lines.length
|
||||
def !! : Option[String] = !-(1)
|
||||
|
||||
def apply(i: Int): Option[String] =
|
||||
if (0 <= i && i < size) Some(lines(i))
|
||||
else {
|
||||
sys.error("Invalid history index: " + i)
|
||||
}
|
||||
|
||||
def !(i: Int): Option[String] = apply(i)
|
||||
|
||||
def !(s: String): Option[String] =
|
||||
number(s) match {
|
||||
case Some(n) => if (n < 0) !-(-n) else apply(n)
|
||||
case None => nonEmpty(s) { reversed.find(_.startsWith(s)) }
|
||||
}
|
||||
|
||||
def !-(n: Int): Option[String] = apply(size - n - 1)
|
||||
|
||||
def !?(s: String): Option[String] = nonEmpty(s) { reversed.drop(1).find(_.contains(s)) }
|
||||
|
||||
private def nonEmpty[T](s: String)(act: => Option[T]): Option[T] =
|
||||
if (s.isEmpty)
|
||||
sys.error("No action specified to history command")
|
||||
else
|
||||
act
|
||||
|
||||
def list(historySize: Int, show: Int): Seq[String] =
|
||||
lines.toList
|
||||
.drop(scala.math.max(0, lines.size - historySize))
|
||||
.zipWithIndex
|
||||
.map { case (line, number) => " " + number + " " + line }
|
||||
.takeRight(show max 1)
|
||||
}
|
||||
|
||||
object History {
|
||||
def apply(lines: Seq[String], path: Option[File], error: String => Unit): History =
|
||||
new History(lines.toIndexedSeq, path)
|
||||
def apply(lines: Seq[String], path: Option[File]): History =
|
||||
new History(lines.toIndexedSeq, path)
|
||||
|
||||
def number(s: String): Option[Int] =
|
||||
try {
|
||||
Some(s.toInt)
|
||||
} catch { case _: NumberFormatException => None }
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import sbt.io.IO
|
||||
import Util.{ AnyOps, nil }
|
||||
|
||||
object HistoryCommands {
|
||||
val Start = "!"
|
||||
// second characters
|
||||
val Contains = "?"
|
||||
val Last = "!"
|
||||
val ListCommands = ":"
|
||||
|
||||
def ContainsFull = h(Contains)
|
||||
def LastFull = h(Last)
|
||||
def ListFull = h(ListCommands)
|
||||
|
||||
def ListN = ListFull + "n"
|
||||
def ContainsString = ContainsFull + "string"
|
||||
def StartsWithString = Start + "string"
|
||||
def Previous = Start + "-n"
|
||||
def Nth = Start + "n"
|
||||
|
||||
private def h(s: String) = Start + s
|
||||
def plainCommands = Seq(ListFull, Start, LastFull, ContainsFull)
|
||||
|
||||
def descriptions = Seq(
|
||||
LastFull -> "Execute the last command again",
|
||||
ListFull -> "Show all previous commands",
|
||||
ListN -> "Show the last n commands",
|
||||
Nth -> ("Execute the command with index n, as shown by the " + ListFull + " command"),
|
||||
Previous -> "Execute the nth command before this one",
|
||||
StartsWithString -> "Execute the most recent command starting with 'string'",
|
||||
ContainsString -> "Execute the most recent command containing 'string'"
|
||||
)
|
||||
|
||||
def helpString =
|
||||
"History commands:\n " + (descriptions
|
||||
.map { case (c, d) => c + " " + d })
|
||||
.mkString("\n ")
|
||||
|
||||
def printHelp(): Unit = println(helpString)
|
||||
|
||||
def printHistory(history: complete.History, historySize: Int, show: Int): Unit =
|
||||
history.list(historySize, show).foreach(println)
|
||||
|
||||
import DefaultParsers._
|
||||
|
||||
val MaxLines = 500
|
||||
lazy val num = token(NatBasic, "<integer>")
|
||||
lazy val last = Last ^^^ { execute(_.!!) }
|
||||
|
||||
lazy val list = ListCommands ~> (num ?? Int.MaxValue) map { show => (h: History) =>
|
||||
{ printHistory(h, MaxLines, show); nil[String].some }
|
||||
}
|
||||
|
||||
lazy val execStr = flag('?') ~ token(any.+.string, "<string>") map {
|
||||
case (contains, str) =>
|
||||
execute(h => if (contains) h !? str else h ! str)
|
||||
}
|
||||
|
||||
lazy val execInt = flag('-') ~ num map {
|
||||
case (neg, value) =>
|
||||
execute(h => if (neg) h !- value else h ! value)
|
||||
}
|
||||
|
||||
lazy val help = success((h: History) => { printHelp(); nil[String].some })
|
||||
|
||||
def execute(f: History => Option[String]): History => Option[List[String]] = (h: History) => {
|
||||
val command = f(h).filterNot(_.startsWith(Start))
|
||||
val lines = h.lines.toArray
|
||||
command.foreach(lines(lines.length - 1) = _)
|
||||
h.path foreach { h =>
|
||||
IO.writeLines(h, lines)
|
||||
}
|
||||
command.toList.some
|
||||
}
|
||||
|
||||
val actionParser: Parser[complete.History => Option[List[String]]] =
|
||||
Start ~> (help | last | execInt | list | execStr) // execStr must come last
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import jline.console.ConsoleReader
|
||||
import jline.console.completer.{ Completer, CompletionHandler }
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
object JLineCompletion {
|
||||
def installCustomCompletor(reader: ConsoleReader, parser: Parser[_]): Unit =
|
||||
installCustomCompletor(reader)(parserAsCompletor(parser))
|
||||
|
||||
def installCustomCompletor(reader: ConsoleReader)(
|
||||
complete: (String, Int) => (Seq[String], Seq[String])
|
||||
): Unit =
|
||||
installCustomCompletor(customCompletor(complete), reader)
|
||||
|
||||
def installCustomCompletor(
|
||||
complete: (ConsoleReader, Int) => Boolean,
|
||||
reader: ConsoleReader
|
||||
): Unit = {
|
||||
reader.removeCompleter(DummyCompletor)
|
||||
reader.addCompleter(DummyCompletor)
|
||||
reader.setCompletionHandler(new CustomHandler(complete))
|
||||
}
|
||||
|
||||
private[this] final class CustomHandler(completeImpl: (ConsoleReader, Int) => Boolean)
|
||||
extends CompletionHandler {
|
||||
private[this] var previous: Option[(String, Int)] = None
|
||||
private[this] var level: Int = 1
|
||||
|
||||
override def complete(
|
||||
reader: ConsoleReader,
|
||||
candidates: java.util.List[CharSequence],
|
||||
position: Int
|
||||
) = {
|
||||
val current = Some(bufferSnapshot(reader))
|
||||
level = if (current == previous) level + 1 else 1
|
||||
previous = current
|
||||
try completeImpl(reader, level)
|
||||
catch {
|
||||
case e: Exception =>
|
||||
reader.print("\nException occurred while determining completions.")
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// always provides dummy completions so that the custom completion handler gets called
|
||||
// (ConsoleReader doesn't call the handler if there aren't any completions)
|
||||
// the custom handler will then throw away the candidates and call the custom function
|
||||
private[this] final object DummyCompletor extends Completer {
|
||||
override def complete(
|
||||
buffer: String,
|
||||
cursor: Int,
|
||||
candidates: java.util.List[CharSequence]
|
||||
): Int = {
|
||||
candidates.asInstanceOf[java.util.List[String]] add "dummy"
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
def parserAsCompletor(p: Parser[_]): (String, Int) => (Seq[String], Seq[String]) =
|
||||
(str, level) => convertCompletions(Parser.completions(p, str, level))
|
||||
|
||||
def convertCompletions(c: Completions): (Seq[String], Seq[String]) = {
|
||||
val cs = c.get
|
||||
if (cs.isEmpty)
|
||||
(Nil, "{invalid input}" :: Nil)
|
||||
else
|
||||
convertCompletions(cs)
|
||||
}
|
||||
|
||||
def convertCompletions(cs: Set[Completion]): (Seq[String], Seq[String]) = {
|
||||
val (insert, display) =
|
||||
cs.foldLeft((Set.empty[String], Set.empty[String])) {
|
||||
case (t @ (insert, display), comp) =>
|
||||
if (comp.isEmpty) t
|
||||
else (appendNonEmpty(insert, comp.append), appendNonEmpty(display, comp.display))
|
||||
}
|
||||
(insert.toSeq, display.toSeq.sorted)
|
||||
}
|
||||
|
||||
def appendNonEmpty(set: Set[String], add: String) = if (add.trim.isEmpty) set else set + add
|
||||
|
||||
def customCompletor(
|
||||
f: (String, Int) => (Seq[String], Seq[String])
|
||||
): (ConsoleReader, Int) => Boolean =
|
||||
(reader, level) => {
|
||||
val success = complete(beforeCursor(reader), string => f(string, level), reader)
|
||||
reader.flush()
|
||||
success
|
||||
}
|
||||
|
||||
def bufferSnapshot(reader: ConsoleReader): (String, Int) = {
|
||||
val b = reader.getCursorBuffer
|
||||
(b.buffer.toString, b.cursor)
|
||||
}
|
||||
|
||||
def beforeCursor(reader: ConsoleReader): String = {
|
||||
val b = reader.getCursorBuffer
|
||||
b.buffer.substring(0, b.cursor)
|
||||
}
|
||||
|
||||
// returns false if there was nothing to insert and nothing to display
|
||||
def complete(
|
||||
beforeCursor: String,
|
||||
completions: String => (Seq[String], Seq[String]),
|
||||
reader: ConsoleReader
|
||||
): Boolean = {
|
||||
val (insert, display) = completions(beforeCursor)
|
||||
val common = commonPrefix(insert)
|
||||
if (common.isEmpty)
|
||||
if (display.isEmpty)
|
||||
()
|
||||
else
|
||||
showCompletions(display, reader)
|
||||
else
|
||||
appendCompletion(common, reader)
|
||||
|
||||
!(common.isEmpty && display.isEmpty)
|
||||
}
|
||||
|
||||
def appendCompletion(common: String, reader: ConsoleReader): Unit = {
|
||||
reader.getCursorBuffer.write(common)
|
||||
reader.redrawLine()
|
||||
}
|
||||
|
||||
/**
|
||||
* `display` is assumed to be the exact strings requested to be displayed.
|
||||
* In particular, duplicates should have been removed already.
|
||||
*/
|
||||
def showCompletions(display: Seq[String], reader: ConsoleReader): Unit = {
|
||||
printCompletions(display, reader)
|
||||
reader.drawLine()
|
||||
}
|
||||
|
||||
def printCompletions(cs: Seq[String], reader: ConsoleReader): Unit = {
|
||||
val print = shouldPrint(cs, reader)
|
||||
reader.println()
|
||||
if (print) printLinesAndColumns(cs, reader)
|
||||
}
|
||||
|
||||
def printLinesAndColumns(cs: Seq[String], reader: ConsoleReader): Unit = {
|
||||
val (lines, columns) = cs partition hasNewline
|
||||
for (line <- lines) {
|
||||
reader.print(line)
|
||||
if (line.charAt(line.length - 1) != '\n')
|
||||
reader.println()
|
||||
}
|
||||
reader.printColumns(columns.map(_.trim).asJava)
|
||||
}
|
||||
|
||||
def hasNewline(s: String): Boolean = s.indexOf('\n') >= 0
|
||||
|
||||
def shouldPrint(cs: Seq[String], reader: ConsoleReader): Boolean = {
|
||||
val size = cs.size
|
||||
(size <= reader.getAutoprintThreshold) ||
|
||||
confirm("Display all %d possibilities? (y or n) ".format(size), 'y', 'n', reader)
|
||||
}
|
||||
|
||||
def confirm(prompt: String, trueC: Char, falseC: Char, reader: ConsoleReader): Boolean = {
|
||||
reader.println()
|
||||
reader.print(prompt)
|
||||
reader.flush()
|
||||
reader.readCharacter(trueC, falseC) == trueC
|
||||
}
|
||||
|
||||
def commonPrefix(s: Seq[String]): String = if (s.isEmpty) "" else s reduceLeft commonPrefix
|
||||
|
||||
def commonPrefix(a: String, b: String): String = {
|
||||
val len = scala.math.min(a.length, b.length)
|
||||
@tailrec def loop(i: Int): Int = if (i >= len) len else if (a(i) != b(i)) i else loop(i + 1)
|
||||
a.substring(0, loop(0))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,988 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import Parser._
|
||||
import sbt.internal.util.Types.{ left, right, some }
|
||||
import sbt.internal.util.Util.{ makeList, separate }
|
||||
|
||||
/**
|
||||
* A String parser that provides semi-automatic tab completion.
|
||||
* A successful parse results in a value of type `T`.
|
||||
* The methods in this trait are what must be implemented to define a new Parser implementation, but are not typically useful for common usage.
|
||||
* Instead, most useful methods for combining smaller parsers into larger parsers are implicitly added by the [[RichParser]] type.
|
||||
*/
|
||||
trait Parser[+T] {
|
||||
def derive(i: Char): Parser[T]
|
||||
def resultEmpty: Result[T]
|
||||
def result: Option[T]
|
||||
def completions(level: Int): Completions
|
||||
def failure: Option[Failure]
|
||||
def isTokenStart = false
|
||||
def ifValid[S](p: => Parser[S]): Parser[S]
|
||||
def valid: Boolean
|
||||
}
|
||||
|
||||
sealed trait RichParser[A] {
|
||||
|
||||
/** Apply the original Parser and then apply `next` (in order). The result of both is provides as a pair. */
|
||||
def ~[B](next: Parser[B]): Parser[(A, B)]
|
||||
|
||||
/** Apply the original Parser one or more times and provide the non-empty sequence of results.*/
|
||||
def + : Parser[Seq[A]]
|
||||
|
||||
/** Apply the original Parser zero or more times and provide the (potentially empty) sequence of results.*/
|
||||
def * : Parser[Seq[A]]
|
||||
|
||||
/** Apply the original Parser zero or one times, returning None if it was applied zero times or the result wrapped in Some if it was applied once.*/
|
||||
def ? : Parser[Option[A]]
|
||||
|
||||
/** Apply either the original Parser or `b`.*/
|
||||
def |[B >: A](b: Parser[B]): Parser[B]
|
||||
|
||||
/** Apply either the original Parser or `b`.*/
|
||||
def ||[B](b: Parser[B]): Parser[Either[A, B]]
|
||||
|
||||
/** Apply the original Parser to the input and then apply `f` to the result.*/
|
||||
def map[B](f: A => B): Parser[B]
|
||||
|
||||
/**
|
||||
* Returns the original parser. This is useful for converting literals to Parsers.
|
||||
* For example, `'c'.id` or `"asdf".id`
|
||||
*/
|
||||
def id: Parser[A]
|
||||
|
||||
/** Apply the original Parser, but provide `value` as the result if it succeeds. */
|
||||
def ^^^[B](value: B): Parser[B]
|
||||
|
||||
/** Apply the original Parser, but provide `alt` as the result if it fails.*/
|
||||
def ??[B >: A](alt: B): Parser[B]
|
||||
|
||||
/**
|
||||
* Produces a Parser that applies the original Parser and then applies `next` (in order), discarding the result of `next`.
|
||||
* (The arrow point in the direction of the retained result.)
|
||||
*/
|
||||
def <~[B](b: Parser[B]): Parser[A]
|
||||
|
||||
/**
|
||||
* Produces a Parser that applies the original Parser and then applies `next` (in order), discarding the result of the original parser.
|
||||
* (The arrow point in the direction of the retained result.)
|
||||
*/
|
||||
def ~>[B](b: Parser[B]): Parser[B]
|
||||
|
||||
/** Uses the specified message if the original Parser fails.*/
|
||||
def !!!(msg: String): Parser[A]
|
||||
|
||||
/**
|
||||
* If an exception is thrown by the original Parser,
|
||||
* capture it and fail locally instead of allowing the exception to propagate up and terminate parsing.
|
||||
*/
|
||||
def failOnException: Parser[A]
|
||||
|
||||
/**
|
||||
* Apply the original parser, but only succeed if `o` also succeeds.
|
||||
* Note that `o` does not need to consume the same amount of input to satisfy this condition.
|
||||
*/
|
||||
def &(o: Parser[_]): Parser[A]
|
||||
|
||||
/** Explicitly defines the completions for the original Parser.*/
|
||||
def examples(s: String*): Parser[A]
|
||||
|
||||
/** Explicitly defines the completions for the original Parser.*/
|
||||
def examples(s: Set[String], check: Boolean = false): Parser[A]
|
||||
|
||||
/**
|
||||
* @param exampleSource the source of examples when displaying completions to the user.
|
||||
* @param maxNumberOfExamples limits the number of examples that the source of examples should return. This can
|
||||
* prevent lengthy pauses and avoids bad interactive user experience.
|
||||
* @param removeInvalidExamples indicates whether completion examples should be checked for validity (against the
|
||||
* given parser). Invalid examples will be filtered out and only valid suggestions will
|
||||
* be displayed.
|
||||
* @return a new parser with a new source of completions.
|
||||
*/
|
||||
def examples(
|
||||
exampleSource: ExampleSource,
|
||||
maxNumberOfExamples: Int,
|
||||
removeInvalidExamples: Boolean
|
||||
): Parser[A]
|
||||
|
||||
/**
|
||||
* @param exampleSource the source of examples when displaying completions to the user.
|
||||
* @return a new parser with a new source of completions. It displays at most 25 completion examples and does not
|
||||
* remove invalid examples.
|
||||
*/
|
||||
def examples(exampleSource: ExampleSource): Parser[A] =
|
||||
examples(exampleSource, maxNumberOfExamples = 25, removeInvalidExamples = false)
|
||||
|
||||
/** Converts a Parser returning a Char sequence to a Parser returning a String.*/
|
||||
def string(implicit ev: A <:< Seq[Char]): Parser[String]
|
||||
|
||||
/**
|
||||
* Produces a Parser that filters the original parser.
|
||||
* If 'f' is not true when applied to the output of the original parser, the Parser returned by this method fails.
|
||||
* The failure message is constructed by applying `msg` to the String that was successfully parsed by the original parser.
|
||||
*/
|
||||
def filter(f: A => Boolean, msg: String => String): Parser[A]
|
||||
|
||||
/** Applies the original parser, applies `f` to the result to get the next parser, and applies that parser and uses its result for the overall result. */
|
||||
def flatMap[B](f: A => Parser[B]): Parser[B]
|
||||
}
|
||||
|
||||
/** Contains Parser implementation helper methods not typically needed for using parsers. */
|
||||
object Parser extends ParserMain {
|
||||
sealed abstract class Result[+T] {
|
||||
def isFailure: Boolean
|
||||
def isValid: Boolean
|
||||
def errors: Seq[String]
|
||||
def or[B >: T](b: => Result[B]): Result[B]
|
||||
def either[B](b: => Result[B]): Result[Either[T, B]]
|
||||
def map[B](f: T => B): Result[B]
|
||||
def flatMap[B](f: T => Result[B]): Result[B]
|
||||
def &&(b: => Result[_]): Result[T]
|
||||
def filter(f: T => Boolean, msg: => String): Result[T]
|
||||
def seq[B](b: => Result[B]): Result[(T, B)] = app(b)((m, n) => (m, n))
|
||||
def app[B, C](b: => Result[B])(f: (T, B) => C): Result[C]
|
||||
def toEither: Either[() => Seq[String], T]
|
||||
}
|
||||
|
||||
final case class Value[+T](value: T) extends Result[T] {
|
||||
def isFailure = false
|
||||
def isValid: Boolean = true
|
||||
def errors = Nil
|
||||
|
||||
def app[B, C](b: => Result[B])(f: (T, B) => C): Result[C] = b match {
|
||||
case fail: Failure => fail
|
||||
case Value(bv) => Value(f(value, bv))
|
||||
}
|
||||
|
||||
def &&(b: => Result[_]): Result[T] = b match { case f: Failure => f; case _ => this }
|
||||
def or[B >: T](b: => Result[B]): Result[B] = this
|
||||
def either[B](b: => Result[B]): Result[Either[T, B]] = Value(Left(value))
|
||||
def map[B](f: T => B): Result[B] = Value(f(value))
|
||||
def flatMap[B](f: T => Result[B]): Result[B] = f(value)
|
||||
def filter(f: T => Boolean, msg: => String): Result[T] = if (f(value)) this else mkFailure(msg)
|
||||
def toEither = Right(value)
|
||||
}
|
||||
|
||||
final class Failure private[sbt] (mkErrors: => Seq[String], val definitive: Boolean)
|
||||
extends Result[Nothing] {
|
||||
lazy val errors: Seq[String] = mkErrors
|
||||
def isFailure = true
|
||||
def isValid = false
|
||||
def map[B](f: Nothing => B) = this
|
||||
def flatMap[B](f: Nothing => Result[B]) = this
|
||||
|
||||
def or[B](b: => Result[B]): Result[B] = b match {
|
||||
case v: Value[B] => v
|
||||
case f: Failure => if (definitive) this else this ++ f
|
||||
}
|
||||
|
||||
def either[B](b: => Result[B]): Result[Either[Nothing, B]] = b match {
|
||||
case Value(v) => Value(Right(v))
|
||||
case f: Failure => if (definitive) this else this ++ f
|
||||
}
|
||||
|
||||
def filter(f: Nothing => Boolean, msg: => String) = this
|
||||
def app[B, C](b: => Result[B])(f: (Nothing, B) => C): Result[C] = this
|
||||
def &&(b: => Result[_]) = this
|
||||
def toEither = Left(() => errors)
|
||||
|
||||
private[sbt] def ++(f: Failure) = mkFailures(errors ++ f.errors)
|
||||
}
|
||||
|
||||
def mkFailures(errors: => Seq[String], definitive: Boolean = false): Failure =
|
||||
new Failure(errors.distinct, definitive)
|
||||
|
||||
def mkFailure(error: => String, definitive: Boolean = false): Failure =
|
||||
new Failure(error :: Nil, definitive)
|
||||
|
||||
def tuple[A, B](a: Option[A], b: Option[B]): Option[(A, B)] =
|
||||
(a, b) match { case (Some(av), Some(bv)) => Some((av, bv)); case _ => None }
|
||||
|
||||
def mapParser[A, B](a: Parser[A], f: A => B): Parser[B] =
|
||||
a.ifValid {
|
||||
a.result match {
|
||||
case Some(av) => success(f(av))
|
||||
case None =>
|
||||
a match {
|
||||
case m: MapParser[_, A] => m.map(f)
|
||||
case _ => new MapParser(a, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def bindParser[A, B](a: Parser[A], f: A => Parser[B]): Parser[B] =
|
||||
a.ifValid {
|
||||
a.result match {
|
||||
case Some(av) => f(av)
|
||||
case None => new BindParser(a, f)
|
||||
}
|
||||
}
|
||||
|
||||
def filterParser[T](
|
||||
a: Parser[T],
|
||||
f: T => Boolean,
|
||||
seen: String,
|
||||
msg: String => String
|
||||
): Parser[T] =
|
||||
a.ifValid {
|
||||
a.result match {
|
||||
case Some(av) if f(av) => success(av)
|
||||
case _ => new Filter(a, f, seen, msg)
|
||||
}
|
||||
}
|
||||
|
||||
def seqParser[A, B](a: Parser[A], b: Parser[B]): Parser[(A, B)] =
|
||||
a.ifValid {
|
||||
b.ifValid {
|
||||
(a.result, b.result) match {
|
||||
case (Some(av), Some(bv)) => success((av, bv))
|
||||
case (Some(av), None) => b map (bv => (av, bv))
|
||||
case (None, Some(bv)) => a map (av => (av, bv))
|
||||
case (None, None) => new SeqParser(a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def choiceParser[A, B](a: Parser[A], b: Parser[B]): Parser[Either[A, B]] =
|
||||
if (a.valid)
|
||||
if (b.valid) new HetParser(a, b) else a.map(left.fn)
|
||||
else
|
||||
b.map(right.fn)
|
||||
|
||||
def opt[T](a: Parser[T]): Parser[Option[T]] =
|
||||
if (a.valid) new Optional(a) else success(None)
|
||||
|
||||
def onFailure[T](delegate: Parser[T], msg: String): Parser[T] =
|
||||
if (delegate.valid) new OnFailure(delegate, msg) else failure(msg)
|
||||
|
||||
def trapAndFail[T](delegate: Parser[T]): Parser[T] =
|
||||
delegate.ifValid(new TrapAndFail(delegate))
|
||||
|
||||
def zeroOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 0, Infinite)
|
||||
def oneOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 1, Infinite)
|
||||
|
||||
def repeat[T](p: Parser[T], min: Int = 0, max: UpperBound = Infinite): Parser[Seq[T]] =
|
||||
repeat(None, p, min, max, Nil)
|
||||
|
||||
private[complete] def repeat[T](
|
||||
partial: Option[Parser[T]],
|
||||
repeated: Parser[T],
|
||||
min: Int,
|
||||
max: UpperBound,
|
||||
revAcc: List[T]
|
||||
): Parser[Seq[T]] = {
|
||||
assume(min >= 0, "Minimum must be greater than or equal to zero (was " + min + ")")
|
||||
assume(
|
||||
max >= min,
|
||||
"Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")"
|
||||
)
|
||||
|
||||
def checkRepeated(invalidButOptional: => Parser[Seq[T]]): Parser[Seq[T]] =
|
||||
repeated match {
|
||||
case _: Invalid if min == 0 => invalidButOptional
|
||||
case i: Invalid => i
|
||||
case _ =>
|
||||
repeated.result match {
|
||||
case Some(value) =>
|
||||
success(revAcc reverse_::: value :: Nil) // revAcc should be Nil here
|
||||
case None =>
|
||||
if (max.isZero) success(revAcc.reverse)
|
||||
else new Repeat(partial, repeated, min, max, revAcc)
|
||||
}
|
||||
}
|
||||
|
||||
partial match {
|
||||
case Some(part) =>
|
||||
part.ifValid {
|
||||
part.result match {
|
||||
case Some(value) => repeat(None, repeated, min, max, value :: revAcc)
|
||||
case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse))
|
||||
}
|
||||
}
|
||||
case None => checkRepeated(success(Nil))
|
||||
}
|
||||
}
|
||||
|
||||
def and[T](a: Parser[T], b: Parser[_]): Parser[T] = a.ifValid(b.ifValid(new And(a, b)))
|
||||
}
|
||||
|
||||
trait ParserMain {
|
||||
|
||||
/** Provides combinators for Parsers.*/
|
||||
implicit def richParser[A](a: Parser[A]): RichParser[A] = new RichParser[A] {
|
||||
def ~[B](b: Parser[B]) = seqParser(a, b)
|
||||
def ||[B](b: Parser[B]) = choiceParser(a, b)
|
||||
def |[B >: A](b: Parser[B]) = homParser[B](a, b)
|
||||
def ? = opt(a)
|
||||
def * = zeroOrMore(a)
|
||||
def + = oneOrMore(a)
|
||||
def map[B](f: A => B) = mapParser(a, f)
|
||||
def id = a
|
||||
|
||||
def ^^^[B](value: B): Parser[B] = a map (_ => value)
|
||||
def ??[B >: A](alt: B): Parser[B] = a.? map { x =>
|
||||
x.getOrElse[B](alt)
|
||||
}
|
||||
def <~[B](b: Parser[B]): Parser[A] = (a ~ b) map { case av ~ _ => av }
|
||||
def ~>[B](b: Parser[B]): Parser[B] = (a ~ b) map { case _ ~ bv => bv }
|
||||
def !!!(msg: String): Parser[A] = onFailure(a, msg)
|
||||
def failOnException: Parser[A] = trapAndFail(a)
|
||||
|
||||
def &(o: Parser[_]) = and(a, o)
|
||||
def examples(s: String*): Parser[A] = examples(s.toSet)
|
||||
|
||||
def examples(s: Set[String], check: Boolean = false): Parser[A] =
|
||||
examples(new FixedSetExamples(s), s.size, check)
|
||||
|
||||
def examples(
|
||||
s: ExampleSource,
|
||||
maxNumberOfExamples: Int,
|
||||
removeInvalidExamples: Boolean
|
||||
): Parser[A] =
|
||||
Parser.examples(a, s, maxNumberOfExamples, removeInvalidExamples)
|
||||
|
||||
def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg)
|
||||
def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
|
||||
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
|
||||
}
|
||||
|
||||
implicit def literalRichCharParser(c: Char): RichParser[Char] = richParser(c)
|
||||
implicit def literalRichStringParser(s: String): RichParser[String] = richParser(s)
|
||||
|
||||
/**
|
||||
* Construct a parser that is valid, but has no valid result. This is used as a way
|
||||
* to provide a definitive Failure when a parser doesn't match empty input. For example,
|
||||
* in `softFailure(...) | p`, if `p` doesn't match the empty sequence, the failure will come
|
||||
* from the Parser constructed by the `softFailure` method.
|
||||
*/
|
||||
private[sbt] def softFailure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
|
||||
SoftInvalid(mkFailures(msg :: Nil, definitive))
|
||||
|
||||
/**
|
||||
* Defines a parser that always fails on any input with messages `msgs`.
|
||||
* If `definitive` is `true`, any failures by later alternatives are discarded.
|
||||
*/
|
||||
def invalid(msgs: => Seq[String], definitive: Boolean = false): Parser[Nothing] =
|
||||
Invalid(mkFailures(msgs, definitive))
|
||||
|
||||
/**
|
||||
* Defines a parser that always fails on any input with message `msg`.
|
||||
* If `definitive` is `true`, any failures by later alternatives are discarded.
|
||||
*/
|
||||
def failure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
|
||||
invalid(msg :: Nil, definitive)
|
||||
|
||||
/** Defines a parser that always succeeds on empty input with the result `value`.*/
|
||||
def success[T](value: T): Parser[T] = new ValidParser[T] {
|
||||
override def result = Some(value)
|
||||
def resultEmpty = Value(value)
|
||||
def derive(c: Char) = Parser.failure("Expected end of input.")
|
||||
def completions(level: Int) = Completions.empty
|
||||
override def toString = "success(" + value + ")"
|
||||
}
|
||||
|
||||
/** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/
|
||||
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = {
|
||||
val label = r.map(_.toString).toString
|
||||
range(r, label)
|
||||
}
|
||||
|
||||
/** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/
|
||||
def range(r: collection.immutable.NumericRange[Char], label: String): Parser[Char] =
|
||||
charClass(r contains _, label).examples(r.map(_.toString): _*)
|
||||
|
||||
/** Defines a Parser that parses a single character only if it is contained in `legal`.*/
|
||||
def chars(legal: String): Parser[Char] = {
|
||||
val set = legal.toSet
|
||||
charClass(set, "character in '" + legal + "'") examples (set.map(_.toString))
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a Parser that parses a single character only if the predicate `f` returns true for that character.
|
||||
* If this parser fails, `label` is used as the failure message.
|
||||
*/
|
||||
def charClass(f: Char => Boolean, label: String = "<unspecified>"): Parser[Char] =
|
||||
new CharacterClass(f, label)
|
||||
|
||||
/** Presents a single Char `ch` as a Parser that only parses that exact character. */
|
||||
implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char] {
|
||||
def result = None
|
||||
def resultEmpty = mkFailure("Expected '" + ch + "'")
|
||||
def derive(c: Char) = if (c == ch) success(ch) else new Invalid(resultEmpty)
|
||||
def completions(level: Int) = Completions.single(Completion.suggestion(ch.toString))
|
||||
override def toString = "'" + ch + "'"
|
||||
}
|
||||
|
||||
/** Presents a literal String `s` as a Parser that only parses that exact text and provides it as the result.*/
|
||||
implicit def literal(s: String): Parser[String] = stringLiteral(s, 0)
|
||||
|
||||
/** See [[unapply]]. */
|
||||
object ~ {
|
||||
|
||||
/** Convenience for destructuring a tuple that mirrors the `~` combinator.*/
|
||||
def unapply[A, B](t: (A, B)): Some[(A, B)] = Some(t)
|
||||
|
||||
}
|
||||
|
||||
/** Parses input `str` using `parser`. If successful, the result is provided wrapped in `Right`. If unsuccessful, an error message is provided in `Left`.*/
|
||||
def parse[T](str: String, parser: Parser[T]): Either[String, T] =
|
||||
Parser.result(parser, str).left.map { failures =>
|
||||
val (msgs, pos) = failures()
|
||||
ProcessError(str, msgs, pos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to use when developing a parser.
|
||||
* `parser` is applied to the input `str`.
|
||||
* If `completions` is true, the available completions for the input are displayed.
|
||||
* Otherwise, the result of parsing is printed using the result's `toString` method.
|
||||
* If parsing fails, the error message is displayed.
|
||||
*
|
||||
* See also [[sampleParse]] and [[sampleCompletions]].
|
||||
*/
|
||||
def sample(str: String, parser: Parser[_], completions: Boolean = false): Unit =
|
||||
if (completions) sampleCompletions(str, parser) else sampleParse(str, parser)
|
||||
|
||||
/**
|
||||
* Convenience method to use when developing a parser.
|
||||
* `parser` is applied to the input `str` and the result of parsing is printed using the result's `toString` method.
|
||||
* If parsing fails, the error message is displayed.
|
||||
*/
|
||||
def sampleParse(str: String, parser: Parser[_]): Unit =
|
||||
parse(str, parser) match {
|
||||
case Left(msg) => println(msg)
|
||||
case Right(v) => println(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to use when developing a parser.
|
||||
* `parser` is applied to the input `str` and the available completions are displayed on separate lines.
|
||||
* If parsing fails, the error message is displayed.
|
||||
*/
|
||||
def sampleCompletions(str: String, parser: Parser[_], level: Int = 1): Unit =
|
||||
Parser.completions(parser, str, level).get foreach println
|
||||
|
||||
// intended to be temporary pending proper error feedback
|
||||
def result[T](p: Parser[T], s: String): Either[() => (Seq[String], Int), T] = {
|
||||
def loop(i: Int, a: Parser[T]): Either[() => (Seq[String], Int), T] =
|
||||
a match {
|
||||
case Invalid(f) => Left(() => (f.errors, i))
|
||||
case _ =>
|
||||
val ci = i + 1
|
||||
if (ci >= s.length)
|
||||
a.resultEmpty.toEither.left.map { msgs0 => () =>
|
||||
val msgs = msgs0()
|
||||
val nonEmpty = if (msgs.isEmpty) Seq("Unexpected end of input") else msgs
|
||||
(nonEmpty, ci)
|
||||
} else
|
||||
loop(ci, a derive s(ci))
|
||||
}
|
||||
loop(-1, p)
|
||||
}
|
||||
|
||||
/** Applies parser `p` to input `s`. */
|
||||
def apply[T](p: Parser[T])(s: String): Parser[T] =
|
||||
s.foldLeft(p)(derive1)
|
||||
|
||||
/** Applies parser `p` to a single character of input. */
|
||||
def derive1[T](p: Parser[T], c: Char): Parser[T] =
|
||||
if (p.valid) p.derive(c) else p
|
||||
|
||||
/**
|
||||
* Applies parser `p` to input `s` and returns the completions at verbosity `level`.
|
||||
* The interpretation of `level` is up to parser definitions, but 0 is the default by convention,
|
||||
* with increasing positive numbers corresponding to increasing verbosity. Typically no more than
|
||||
* a few levels are defined.
|
||||
*/
|
||||
def completions(p: Parser[_], s: String, level: Int): Completions =
|
||||
// The x Completions.empty removes any trailing token completions where append.isEmpty
|
||||
apply(p)(s).completions(level) x Completions.empty
|
||||
|
||||
def examples[A](a: Parser[A], completions: Set[String], check: Boolean = false): Parser[A] =
|
||||
examples(a, new FixedSetExamples(completions), completions.size, check)
|
||||
|
||||
/**
|
||||
* @param a the parser to decorate with a source of examples. All validation and parsing is delegated to this parser,
|
||||
* only [[Parser.completions]] is modified.
|
||||
* @param completions the source of examples when displaying completions to the user.
|
||||
* @param maxNumberOfExamples limits the number of examples that the source of examples should return. This can
|
||||
* prevent lengthy pauses and avoids bad interactive user experience.
|
||||
* @param removeInvalidExamples indicates whether completion examples should be checked for validity (against the given parser). An
|
||||
* exception is thrown if the example source contains no valid completion suggestions.
|
||||
* @tparam A the type of values that are returned by the parser.
|
||||
* @return
|
||||
*/
|
||||
def examples[A](
|
||||
a: Parser[A],
|
||||
completions: ExampleSource,
|
||||
maxNumberOfExamples: Int,
|
||||
removeInvalidExamples: Boolean
|
||||
): Parser[A] =
|
||||
if (a.valid) {
|
||||
a.result match {
|
||||
case Some(av) => success(av)
|
||||
case None =>
|
||||
new ParserWithExamples(a, completions, maxNumberOfExamples, removeInvalidExamples)
|
||||
}
|
||||
} else a
|
||||
|
||||
def matched(
|
||||
t: Parser[_],
|
||||
seen: Vector[Char] = Vector.empty,
|
||||
partial: Boolean = false
|
||||
): Parser[String] =
|
||||
t match {
|
||||
case i: Invalid => if (partial && seen.nonEmpty) success(seen.mkString) else i
|
||||
case _ =>
|
||||
if (t.result.isEmpty)
|
||||
new MatchedString(t, seen, partial)
|
||||
else
|
||||
success(seen.mkString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes delegate parser `t` as a single token of tab completion.
|
||||
* When tab completion of part of this token is requested, the completions provided by the delegate `t` or a later derivative are appended to
|
||||
* the prefix String already seen by this parser.
|
||||
*/
|
||||
def token[T](t: Parser[T]): Parser[T] = token(t, TokenCompletions.default)
|
||||
|
||||
/**
|
||||
* Establishes delegate parser `t` as a single token of tab completion.
|
||||
* When tab completion of part of this token is requested, no completions are returned if `hide` returns true for the current tab completion level.
|
||||
* Otherwise, the completions provided by the delegate `t` or a later derivative are appended to the prefix String already seen by this parser.
|
||||
*/
|
||||
def token[T](t: Parser[T], hide: Int => Boolean): Parser[T] =
|
||||
token(t, TokenCompletions.default.hideWhen(hide))
|
||||
|
||||
/**
|
||||
* Establishes delegate parser `t` as a single token of tab completion.
|
||||
* When tab completion of part of this token is requested, `description` is displayed for suggestions and no completions are ever performed.
|
||||
*/
|
||||
def token[T](t: Parser[T], description: String): Parser[T] =
|
||||
token(t, TokenCompletions.displayOnly(description))
|
||||
|
||||
/**
|
||||
* Establishes delegate parser `t` as a single token of tab completion.
|
||||
* When tab completion of part of this token is requested, `display` is used as the printed suggestion, but the completions from the delegate
|
||||
* parser `t` are used to complete if unambiguous.
|
||||
*/
|
||||
def tokenDisplay[T](t: Parser[T], display: String): Parser[T] =
|
||||
token(t, TokenCompletions.overrideDisplay(display))
|
||||
|
||||
def token[T](t: Parser[T], complete: TokenCompletions): Parser[T] =
|
||||
mkToken(t, "", complete)
|
||||
|
||||
private[sbt] def mkToken[T](t: Parser[T], seen: String, complete: TokenCompletions): Parser[T] =
|
||||
if (t.valid && !t.isTokenStart)
|
||||
if (t.result.isEmpty) new TokenStart(t, seen, complete) else t
|
||||
else
|
||||
t
|
||||
|
||||
def homParser[A](a: Parser[A], b: Parser[A]): Parser[A] = (a, b) match {
|
||||
case (Invalid(af), Invalid(bf)) => Invalid(af ++ bf)
|
||||
case (Invalid(_), bv) => bv
|
||||
case (av, Invalid(_)) => av
|
||||
case (_, _) => new HomParser(a, b)
|
||||
}
|
||||
|
||||
def not(p: Parser[_], failMessage: String): Parser[Unit] = p.result match {
|
||||
case None => new Not(p, failMessage)
|
||||
case Some(_) => failure(failMessage)
|
||||
}
|
||||
|
||||
def oneOf[T](p: Seq[Parser[T]]): Parser[T] = p.reduceLeft(_ | _)
|
||||
def seq[T](p: Seq[Parser[T]]): Parser[Seq[T]] = seq0(p, Nil)
|
||||
|
||||
def seq0[T](p: Seq[Parser[T]], errors: => Seq[String]): Parser[Seq[T]] = {
|
||||
val (newErrors, valid) = separate(p) {
|
||||
case Invalid(f) => Left(f.errors _): Either[() => Seq[String], Parser[T]]
|
||||
case ok => Right(ok): Either[() => Seq[String], Parser[T]]
|
||||
}
|
||||
def combinedErrors = errors ++ newErrors.flatMap(_())
|
||||
if (valid.isEmpty) invalid(combinedErrors) else new ParserSeq(valid, combinedErrors)
|
||||
}
|
||||
|
||||
def stringLiteral(s: String, start: Int): Parser[String] = {
|
||||
val len = s.length
|
||||
if (len == 0) sys.error("String literal cannot be empty")
|
||||
else if (start >= len) success(s)
|
||||
else new StringLiteral(s, start)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait ValidParser[T] extends Parser[T] {
|
||||
final def valid = true
|
||||
final def failure = None
|
||||
final def ifValid[S](p: => Parser[S]): Parser[S] = p
|
||||
}
|
||||
|
||||
private final case class Invalid(fail: Failure) extends Parser[Nothing] {
|
||||
def failure = Some(fail)
|
||||
def result = None
|
||||
def resultEmpty = fail
|
||||
def derive(c: Char) = sys.error("Invalid.")
|
||||
def completions(level: Int) = Completions.nil
|
||||
override def toString = fail.errors.mkString("; ")
|
||||
def valid = false
|
||||
def ifValid[S](p: => Parser[S]): Parser[S] = this
|
||||
}
|
||||
|
||||
private final case class SoftInvalid(fail: Failure) extends ValidParser[Nothing] {
|
||||
def result = None
|
||||
def resultEmpty = fail
|
||||
def derive(c: Char) = Invalid(fail)
|
||||
def completions(level: Int) = Completions.nil
|
||||
override def toString = fail.errors.mkString("; ")
|
||||
}
|
||||
|
||||
private final class TrapAndFail[A](a: Parser[A]) extends ValidParser[A] {
|
||||
def result =
|
||||
try {
|
||||
a.result
|
||||
} catch { case _: Exception => None }
|
||||
def resultEmpty =
|
||||
try {
|
||||
a.resultEmpty
|
||||
} catch { case e: Exception => fail(e) }
|
||||
|
||||
def derive(c: Char) =
|
||||
try {
|
||||
trapAndFail(a derive c)
|
||||
} catch {
|
||||
case e: Exception => Invalid(fail(e))
|
||||
}
|
||||
|
||||
def completions(level: Int) =
|
||||
try {
|
||||
a.completions(level)
|
||||
} catch {
|
||||
case _: Exception => Completions.nil
|
||||
}
|
||||
|
||||
override def toString = "trap(" + a + ")"
|
||||
override def isTokenStart = a.isTokenStart
|
||||
private[this] def fail(e: Exception): Failure = mkFailure(e.toString)
|
||||
}
|
||||
|
||||
private final class OnFailure[A](a: Parser[A], message: String) extends ValidParser[A] {
|
||||
def result = a.result
|
||||
|
||||
def resultEmpty = a.resultEmpty match {
|
||||
case _: Failure => mkFailure(message); case v: Value[A] => v
|
||||
}
|
||||
|
||||
def derive(c: Char) = onFailure(a derive c, message)
|
||||
def completions(level: Int) = a.completions(level)
|
||||
override def toString = "(" + a + " !!! \"" + message + "\" )"
|
||||
override def isTokenStart = a.isTokenStart
|
||||
}
|
||||
|
||||
private final class SeqParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[(A, B)] {
|
||||
lazy val result = tuple(a.result, b.result)
|
||||
lazy val resultEmpty = a.resultEmpty seq b.resultEmpty
|
||||
|
||||
def derive(c: Char) = {
|
||||
val common = a.derive(c) ~ b
|
||||
a.resultEmpty match {
|
||||
case Value(av) => common | b.derive(c).map(br => (av, br))
|
||||
case _: Failure => common
|
||||
}
|
||||
}
|
||||
|
||||
def completions(level: Int) = a.completions(level) x b.completions(level)
|
||||
override def toString = "(" + a + " ~ " + b + ")"
|
||||
}
|
||||
|
||||
private final class HomParser[A](a: Parser[A], b: Parser[A]) extends ValidParser[A] {
|
||||
lazy val result = tuple(a.result, b.result) map (_._1)
|
||||
def derive(c: Char) = (a derive c) | (b derive c)
|
||||
lazy val resultEmpty = a.resultEmpty or b.resultEmpty
|
||||
def completions(level: Int) = a.completions(level) ++ b.completions(level)
|
||||
override def toString = "(" + a + " | " + b + ")"
|
||||
}
|
||||
|
||||
private final class HetParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[Either[A, B]] {
|
||||
lazy val result = tuple(a.result, b.result) map { case (a, _) => Left(a) }
|
||||
def derive(c: Char) = (a derive c) || (b derive c)
|
||||
lazy val resultEmpty = a.resultEmpty either b.resultEmpty
|
||||
def completions(level: Int) = a.completions(level) ++ b.completions(level)
|
||||
override def toString = "(" + a + " || " + b + ")"
|
||||
}
|
||||
|
||||
private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String])
|
||||
extends ValidParser[Seq[T]] {
|
||||
assert(a.nonEmpty)
|
||||
|
||||
lazy val resultEmpty: Result[Seq[T]] = {
|
||||
val res = a.map(_.resultEmpty)
|
||||
val (failures, values) = separate(res)(_.toEither)
|
||||
// if(failures.isEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
|
||||
if (values.nonEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
|
||||
}
|
||||
|
||||
def result = {
|
||||
val success = a.flatMap(_.result)
|
||||
if (success.length == a.length) Some(success) else None
|
||||
}
|
||||
|
||||
def completions(level: Int) = a.map(_.completions(level)).reduceLeft(_ ++ _)
|
||||
def derive(c: Char) = seq0(a.map(_ derive c), errors)
|
||||
|
||||
override def toString = "seq(" + a + ")"
|
||||
}
|
||||
|
||||
private final class BindParser[A, B](a: Parser[A], f: A => Parser[B]) extends ValidParser[B] {
|
||||
lazy val result = a.result flatMap (av => f(av).result)
|
||||
lazy val resultEmpty = a.resultEmpty flatMap (av => f(av).resultEmpty)
|
||||
|
||||
def completions(level: Int) =
|
||||
a.completions(level) flatMap { c =>
|
||||
apply(a)(c.append).resultEmpty match {
|
||||
case _: Failure => Completions.strict(Set.empty + c)
|
||||
case Value(av) => c x f(av).completions(level)
|
||||
}
|
||||
}
|
||||
|
||||
def derive(c: Char) = {
|
||||
val common = a derive c flatMap f
|
||||
a.resultEmpty match {
|
||||
case Value(av) => common | derive1(f(av), c)
|
||||
case _: Failure => common
|
||||
}
|
||||
}
|
||||
|
||||
override def isTokenStart = a.isTokenStart
|
||||
|
||||
override def toString = "bind(" + a + ")"
|
||||
}
|
||||
|
||||
private final class MapParser[A, B](a: Parser[A], f: A => B) extends ValidParser[B] {
|
||||
lazy val result = a.result map f
|
||||
lazy val resultEmpty = a.resultEmpty map f
|
||||
def derive(c: Char) = (a derive c) map f
|
||||
def completions(level: Int) = a.completions(level)
|
||||
override def isTokenStart = a.isTokenStart
|
||||
override def toString = "map(" + a + ")"
|
||||
def map[C](g: B => C) = new MapParser[A, C](a, f.andThen(g))
|
||||
}
|
||||
|
||||
private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg: String => String)
|
||||
extends ValidParser[T] {
|
||||
def filterResult(r: Result[T]) = r.filter(f, msg(seen))
|
||||
lazy val result = p.result filter f
|
||||
lazy val resultEmpty = filterResult(p.resultEmpty)
|
||||
def derive(c: Char) = filterParser(p derive c, f, seen + c, msg)
|
||||
|
||||
def completions(level: Int) = p.completions(level) filterS { s =>
|
||||
filterResult(apply(p)(s).resultEmpty).isValid
|
||||
}
|
||||
|
||||
override def toString = "filter(" + p + ")"
|
||||
override def isTokenStart = p.isTokenStart
|
||||
}
|
||||
|
||||
private final class MatchedString(delegate: Parser[_], seenV: Vector[Char], partial: Boolean)
|
||||
extends ValidParser[String] {
|
||||
lazy val seen = seenV.mkString
|
||||
def derive(c: Char) = matched(delegate derive c, seenV :+ c, partial)
|
||||
def completions(level: Int) = delegate.completions(level)
|
||||
def result = if (delegate.result.isDefined) Some(seen) else None
|
||||
|
||||
def resultEmpty = delegate.resultEmpty match {
|
||||
case f: Failure if !partial => f; case _ => Value(seen)
|
||||
}
|
||||
|
||||
override def isTokenStart = delegate.isTokenStart
|
||||
override def toString = "matched(" + partial + ", " + seen + ", " + delegate + ")"
|
||||
}
|
||||
|
||||
private final class TokenStart[T](delegate: Parser[T], seen: String, complete: TokenCompletions)
|
||||
extends ValidParser[T] {
|
||||
def derive(c: Char) = mkToken(delegate derive c, seen + c, complete)
|
||||
|
||||
def completions(level: Int) = complete match {
|
||||
case dc: TokenCompletions.Delegating =>
|
||||
dc.completions(seen, level, delegate.completions(level))
|
||||
case fc: TokenCompletions.Fixed => fc.completions(seen, level)
|
||||
}
|
||||
|
||||
def result = delegate.result
|
||||
def resultEmpty = delegate.resultEmpty
|
||||
override def isTokenStart = true
|
||||
override def toString = "token('" + complete + ", " + delegate + ")"
|
||||
}
|
||||
|
||||
private final class And[T](a: Parser[T], b: Parser[_]) extends ValidParser[T] {
|
||||
lazy val result = tuple(a.result, b.result) map { _._1 }
|
||||
def derive(c: Char) = (a derive c) & (b derive c)
|
||||
def completions(level: Int) = a.completions(level).filterS(s => apply(b)(s).resultEmpty.isValid)
|
||||
lazy val resultEmpty = a.resultEmpty && b.resultEmpty
|
||||
override def toString = "(%s) && (%s)".format(a, b)
|
||||
}
|
||||
|
||||
private final class Not(delegate: Parser[_], failMessage: String) extends ValidParser[Unit] {
|
||||
def derive(c: Char) = if (delegate.valid) not(delegate derive c, failMessage) else this
|
||||
def completions(level: Int) = Completions.empty
|
||||
def result = None
|
||||
|
||||
lazy val resultEmpty = delegate.resultEmpty match {
|
||||
case _: Failure => Value(())
|
||||
case _: Value[_] => mkFailure(failMessage)
|
||||
}
|
||||
|
||||
override def toString = " -(%s)".format(delegate)
|
||||
}
|
||||
|
||||
/**
|
||||
* This class wraps an existing parser (the delegate), and replaces the delegate's completions with examples from
|
||||
* the given example source.
|
||||
*
|
||||
* This class asks the example source for a limited amount of examples (to prevent lengthy and expensive
|
||||
* computations and large amounts of allocated data). It then passes these examples on to the UI.
|
||||
*
|
||||
* @param delegate the parser to decorate with completion examples (i.e., completion of user input).
|
||||
* @param exampleSource the source from which this class will take examples (potentially filter them with the delegate
|
||||
* parser), and pass them to the UI.
|
||||
* @param maxNumberOfExamples the maximum number of completions to read from the example source and pass to the UI. This
|
||||
* limit prevents lengthy example generation and allocation of large amounts of memory.
|
||||
* @param removeInvalidExamples indicates whether to remove examples that are deemed invalid by the delegate parser.
|
||||
* @tparam T the type of value produced by the parser.
|
||||
*/
|
||||
private final class ParserWithExamples[T](
|
||||
delegate: Parser[T],
|
||||
exampleSource: ExampleSource,
|
||||
maxNumberOfExamples: Int,
|
||||
removeInvalidExamples: Boolean
|
||||
) extends ValidParser[T] {
|
||||
|
||||
def derive(c: Char) =
|
||||
examples(
|
||||
delegate derive c,
|
||||
exampleSource.withAddedPrefix(c.toString),
|
||||
maxNumberOfExamples,
|
||||
removeInvalidExamples
|
||||
)
|
||||
|
||||
def result = delegate.result
|
||||
|
||||
lazy val resultEmpty = delegate.resultEmpty
|
||||
|
||||
def completions(level: Int) = {
|
||||
if (exampleSource().isEmpty)
|
||||
if (resultEmpty.isValid) Completions.nil else Completions.empty
|
||||
else {
|
||||
val examplesBasedOnTheResult = filteredExamples.take(maxNumberOfExamples).toSet
|
||||
Completions(examplesBasedOnTheResult.map(ex => Completion.suggestion(ex)))
|
||||
}
|
||||
}
|
||||
|
||||
override def toString = "examples(" + delegate + ", " + exampleSource().take(2).toList + ")"
|
||||
|
||||
private def filteredExamples: Iterable[String] = {
|
||||
if (removeInvalidExamples)
|
||||
exampleSource().filter(isExampleValid)
|
||||
else
|
||||
exampleSource()
|
||||
}
|
||||
|
||||
private def isExampleValid(example: String): Boolean = {
|
||||
apply(delegate)(example).resultEmpty.isValid
|
||||
}
|
||||
}
|
||||
|
||||
private final class StringLiteral(str: String, start: Int) extends ValidParser[String] {
|
||||
assert(0 <= start && start < str.length)
|
||||
|
||||
def failMsg = "Expected '" + str + "'"
|
||||
def resultEmpty = mkFailure(failMsg)
|
||||
def result = None
|
||||
|
||||
def derive(c: Char) =
|
||||
if (str.charAt(start) == c) stringLiteral(str, start + 1) else new Invalid(resultEmpty)
|
||||
|
||||
def completions(level: Int) = Completions.single(Completion.suggestion(str.substring(start)))
|
||||
override def toString = '"' + str + '"'
|
||||
}
|
||||
|
||||
private final class CharacterClass(f: Char => Boolean, label: String) extends ValidParser[Char] {
|
||||
def result = None
|
||||
def resultEmpty = mkFailure("Expected " + label)
|
||||
def derive(c: Char) = if (f(c)) success(c) else Invalid(resultEmpty)
|
||||
def completions(level: Int) = Completions.empty
|
||||
override def toString = "class(" + label + ")"
|
||||
}
|
||||
|
||||
private final class Optional[T](delegate: Parser[T]) extends ValidParser[Option[T]] {
|
||||
def result = delegate.result map some.fn
|
||||
def resultEmpty = Value(None)
|
||||
def derive(c: Char) = (delegate derive c).map(some.fn)
|
||||
def completions(level: Int) = Completion.empty +: delegate.completions(level)
|
||||
override def toString = delegate.toString + "?"
|
||||
}
|
||||
|
||||
private final class Repeat[T](
|
||||
partial: Option[Parser[T]],
|
||||
repeated: Parser[T],
|
||||
min: Int,
|
||||
max: UpperBound,
|
||||
accumulatedReverse: List[T]
|
||||
) extends ValidParser[Seq[T]] {
|
||||
assume(0 <= min, "Minimum occurrences must be non-negative")
|
||||
assume(max >= min, "Minimum occurrences must be less than the maximum occurrences")
|
||||
|
||||
def derive(c: Char) =
|
||||
partial match {
|
||||
case Some(part) =>
|
||||
val partD = repeat(Some(part derive c), repeated, min, max, accumulatedReverse)
|
||||
part.resultEmpty match {
|
||||
case Value(pv) => partD | repeatDerive(c, pv :: accumulatedReverse)
|
||||
case _: Failure => partD
|
||||
}
|
||||
case None => repeatDerive(c, accumulatedReverse)
|
||||
}
|
||||
|
||||
def repeatDerive(c: Char, accRev: List[T]): Parser[Seq[T]] =
|
||||
repeat(Some(repeated derive c), repeated, scala.math.max(0, min - 1), max.decrement, accRev)
|
||||
|
||||
def completions(level: Int) = {
|
||||
def pow(comp: Completions, exp: Completions, n: Int): Completions =
|
||||
if (n == 1) comp else pow(comp x exp, exp, n - 1)
|
||||
|
||||
val repC = repeated.completions(level)
|
||||
val fin = if (min == 0) Completion.empty +: repC else pow(repC, repC, min)
|
||||
partial match {
|
||||
case Some(p) => p.completions(level) x fin
|
||||
case None => fin
|
||||
}
|
||||
}
|
||||
|
||||
def result = None
|
||||
|
||||
lazy val resultEmpty: Result[Seq[T]] = {
|
||||
val partialAccumulatedOption =
|
||||
partial match {
|
||||
case None => (Value(accumulatedReverse): Result[List[T]])
|
||||
case Some(partialPattern) =>
|
||||
partialPattern.resultEmpty.map(_ :: accumulatedReverse)
|
||||
}
|
||||
(partialAccumulatedOption app repeatedParseEmpty)((x, y) => (x reverse_::: y): Seq[T])
|
||||
}
|
||||
|
||||
private def repeatedParseEmpty: Result[List[T]] = {
|
||||
if (min == 0)
|
||||
Value(Nil)
|
||||
else
|
||||
// forced determinism
|
||||
for (value <- repeated.resultEmpty) yield makeList(min, value)
|
||||
}
|
||||
|
||||
override def toString = "repeat(" + min + "," + max + "," + partial + "," + repeated + ")"
|
||||
}
|
||||
|
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import Parser._
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.lang.Character.{
|
||||
CURRENCY_SYMBOL,
|
||||
DASH_PUNCTUATION,
|
||||
MATH_SYMBOL,
|
||||
MODIFIER_SYMBOL,
|
||||
OTHER_PUNCTUATION,
|
||||
OTHER_SYMBOL,
|
||||
getType
|
||||
}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import sbt.internal.util.Util.nilSeq
|
||||
|
||||
/** Provides standard implementations of commonly useful [[Parser]]s. */
|
||||
trait Parsers {
|
||||
|
||||
/** Matches the end of input, providing no useful result on success. */
|
||||
lazy val EOF = not(any, "Expected EOF")
|
||||
|
||||
/** Parses any single character and provides that character as the result. */
|
||||
lazy val any: Parser[Char] = charClass(_ => true, "any character")
|
||||
|
||||
/** Set that contains each digit in a String representation.*/
|
||||
lazy val DigitSet = Set("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
|
||||
|
||||
/** Parses any single digit and provides that digit as a Char as the result.*/
|
||||
lazy val Digit = charClass(_.isDigit, "digit") examples DigitSet
|
||||
|
||||
/** Set containing Chars for hexadecimal digits 0-9 and A-F (but not a-f). */
|
||||
lazy val HexDigitSet =
|
||||
Set('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||
|
||||
/** Parses a single hexadecimal digit (0-9, a-f, A-F). */
|
||||
lazy val HexDigit = charClass(c => HexDigitSet(c.toUpper), "hex digit") examples HexDigitSet.map(
|
||||
_.toString
|
||||
)
|
||||
|
||||
/** Parses a single letter, according to Char.isLetter, into a Char. */
|
||||
lazy val Letter = charClass(_.isLetter, "letter")
|
||||
|
||||
/** Parses a single letter, according to Char.isUpper, into a Char. */
|
||||
lazy val Upper = charClass(_.isUpper, "upper")
|
||||
|
||||
/** Parses a single letter, according to Char.isLower, into a Char. */
|
||||
lazy val Lower = charClass(_.isLower, "lower")
|
||||
|
||||
/** Parses the first Char in an sbt identifier, which must be a [[Letter]].*/
|
||||
def IDStart = Letter
|
||||
|
||||
/** Parses an identifier Char other than the first character. This includes letters, digits, dash `-`, and underscore `_`.*/
|
||||
lazy val IDChar = charClass(isIDChar, "ID character")
|
||||
|
||||
/** Parses an identifier String, which must start with [[IDStart]] and contain zero or more [[IDChar]]s after that. */
|
||||
lazy val ID = identifier(IDStart, IDChar)
|
||||
|
||||
/** Parses a single operator Char, as allowed by [[isOpChar]]. */
|
||||
lazy val OpChar = charClass(isOpChar, "symbol")
|
||||
|
||||
/** Parses a non-empty operator String, which consists only of characters allowed by [[OpChar]]. */
|
||||
lazy val Op = OpChar.+.string
|
||||
|
||||
/** Parses either an operator String defined by [[Op]] or a non-symbolic identifier defined by [[ID]]. */
|
||||
lazy val OpOrID = ID | Op
|
||||
|
||||
/** Parses a single, non-symbolic Scala identifier Char. Valid characters are letters, digits, and the underscore character `_`. */
|
||||
lazy val ScalaIDChar = charClass(isScalaIDChar, "Scala identifier character")
|
||||
|
||||
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[IDStart]] and contain zero or more [[ScalaIDChar]]s after that.*/
|
||||
lazy val ScalaID = identifier(IDStart, ScalaIDChar)
|
||||
|
||||
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[Upper]] and contain zero or more [[ScalaIDChar]]s after that.*/
|
||||
lazy val CapitalizedID = identifier(Upper, ScalaIDChar)
|
||||
|
||||
/** Parses a String that starts with `start` and is followed by zero or more characters parsed by `rep`.*/
|
||||
def identifier(start: Parser[Char], rep: Parser[Char]): Parser[String] =
|
||||
start ~ rep.* map { case x ~ xs => (x +: xs).mkString }
|
||||
|
||||
def opOrIDSpaced(s: String): Parser[Char] =
|
||||
if (DefaultParsers.matches(ID, s))
|
||||
OpChar | SpaceClass
|
||||
else if (DefaultParsers.matches(Op, s))
|
||||
IDChar | SpaceClass
|
||||
else
|
||||
any
|
||||
|
||||
/** Returns true if `c` an operator character. */
|
||||
def isOpChar(c: Char) = !isDelimiter(c) && isOpType(getType(c))
|
||||
|
||||
def isOpType(cat: Int) = cat match {
|
||||
case MATH_SYMBOL | OTHER_SYMBOL | DASH_PUNCTUATION | OTHER_PUNCTUATION | MODIFIER_SYMBOL |
|
||||
CURRENCY_SYMBOL =>
|
||||
true; case _ => false
|
||||
}
|
||||
|
||||
/** Returns true if `c` is a dash `-`, a letter, digit, or an underscore `_`. */
|
||||
def isIDChar(c: Char) = isScalaIDChar(c) || c == '-'
|
||||
|
||||
/** Returns true if `c` is a letter, digit, or an underscore `_`. */
|
||||
def isScalaIDChar(c: Char) = c.isLetterOrDigit || c == '_'
|
||||
|
||||
def isDelimiter(c: Char) = c match {
|
||||
case '`' | '\'' | '\"' | /*';' | */ ',' | '.' => true; case _ => false
|
||||
}
|
||||
|
||||
/** Matches a single character that is not a whitespace character. */
|
||||
lazy val NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character")
|
||||
|
||||
/** Matches a single whitespace character, as determined by Char.isWhitespace.*/
|
||||
lazy val SpaceClass = charClass(_.isWhitespace, "whitespace character")
|
||||
|
||||
/** Matches a non-empty String consisting of non-whitespace characters. */
|
||||
lazy val NotSpace = NotSpaceClass.+.string
|
||||
|
||||
/** Matches a possibly empty String consisting of non-whitespace characters. */
|
||||
lazy val OptNotSpace = NotSpaceClass.*.string
|
||||
|
||||
/**
|
||||
* Matches a non-empty String consisting of whitespace characters.
|
||||
* The suggested tab completion is a single, constant space character.
|
||||
*/
|
||||
lazy val Space: Parser[Seq[Char]] = SpaceClass.+.examples(" ")
|
||||
|
||||
/**
|
||||
* Matches a possibly empty String consisting of whitespace characters.
|
||||
* The suggested tab completion is a single, constant space character.
|
||||
*/
|
||||
lazy val OptSpace = SpaceClass.*.examples(" ")
|
||||
|
||||
/** Parses a non-empty String that contains only valid URI characters, as defined by [[URIChar]].*/
|
||||
lazy val URIClass = URIChar.+.string !!! "Invalid URI"
|
||||
|
||||
/** Triple-quotes, as used for verbatim quoting.*/
|
||||
lazy val VerbatimDQuotes = "\"\"\""
|
||||
|
||||
/** Double quote character. */
|
||||
lazy val DQuoteChar = '\"'
|
||||
|
||||
/** Backslash character. */
|
||||
lazy val BackslashChar = '\\'
|
||||
|
||||
/** Matches a single double quote. */
|
||||
lazy val DQuoteClass = charClass(_ == DQuoteChar, "double-quote character")
|
||||
|
||||
/** Matches any character except a double quote or whitespace. */
|
||||
lazy val NotDQuoteSpaceClass =
|
||||
charClass({ c: Char =>
|
||||
(c != DQuoteChar) && !c.isWhitespace
|
||||
}, "non-double-quote-space character")
|
||||
|
||||
/** Matches any character except a double quote or backslash. */
|
||||
lazy val NotDQuoteBackslashClass =
|
||||
charClass({ c: Char =>
|
||||
(c != DQuoteChar) && (c != BackslashChar)
|
||||
}, "non-double-quote-backslash character")
|
||||
|
||||
/** Matches a single character that is valid somewhere in a URI. */
|
||||
lazy val URIChar = charClass(alphanum, "alphanum") | chars("_-!.~'()*,;:$&+=?/[]@%#")
|
||||
|
||||
/** Returns true if `c` is an ASCII letter or digit. */
|
||||
def alphanum(c: Char) =
|
||||
('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')
|
||||
|
||||
/**
|
||||
* @param base the directory used for completion proposals (when the user presses the TAB key). Only paths under this
|
||||
* directory will be proposed.
|
||||
* @return the file that was parsed from the input string. The returned path may or may not exist.
|
||||
*/
|
||||
def fileParser(base: File): Parser[File] =
|
||||
OptSpace ~> StringBasic
|
||||
.examples(new FileExamples(base))
|
||||
.map(new File(_))
|
||||
|
||||
/** Parses a port number. Currently, this accepts any integer and presents a tab completion suggestion of `<port>`. */
|
||||
lazy val Port = token(IntBasic, "<port>")
|
||||
|
||||
/** Parses a signed integer. */
|
||||
lazy val IntBasic = mapOrFail('-'.? ~ Digit.+)(Function.tupled(toInt))
|
||||
|
||||
/** Parses an unsigned integer. */
|
||||
lazy val NatBasic = mapOrFail(Digit.+)(_.mkString.toInt)
|
||||
|
||||
private[this] def toInt(neg: Option[Char], digits: Seq[Char]): Int =
|
||||
(neg.toSeq ++ digits).mkString.toInt
|
||||
|
||||
/** Parses the lower-case values `true` and `false` into their corresponding Boolean values. */
|
||||
lazy val Bool = ("true" ^^^ true) | ("false" ^^^ false)
|
||||
|
||||
/**
|
||||
* Parses a potentially quoted String value. The value may be verbatim quoted ([[StringVerbatim]]),
|
||||
* quoted with interpreted escapes ([[StringEscapable]]), or unquoted ([[NotQuoted]]).
|
||||
*/
|
||||
lazy val StringBasic = StringVerbatim | StringEscapable | NotQuoted
|
||||
|
||||
/**
|
||||
* Parses a verbatim quoted String value, discarding the quotes in the result. This kind of quoted text starts with triple quotes `"""`
|
||||
* and ends at the next triple quotes and may contain any character in between.
|
||||
*/
|
||||
lazy val StringVerbatim: Parser[String] = VerbatimDQuotes ~>
|
||||
any.+.string.filter(!_.contains(VerbatimDQuotes), _ => "Invalid verbatim string") <~
|
||||
VerbatimDQuotes
|
||||
|
||||
/**
|
||||
* Parses a string value, interpreting escapes and discarding the surrounding quotes in the result.
|
||||
* See [[EscapeSequence]] for supported escapes.
|
||||
*/
|
||||
lazy val StringEscapable: Parser[String] =
|
||||
(DQuoteChar ~> (NotDQuoteBackslashClass | EscapeSequence).+.string <~ DQuoteChar |
|
||||
(DQuoteChar ~ DQuoteChar) ^^^ "")
|
||||
|
||||
/**
|
||||
* Parses a size unit string. For example, `128K` parsers to `128L * 1024`, and `1.25g` parses
|
||||
* to `1024L * 1024 * 1024 * 5 / 4`.
|
||||
*/
|
||||
lazy val Size: Parser[Long] = SizeParser.value
|
||||
|
||||
/**
|
||||
* Parses a brace enclosed string and, if each opening brace is matched with a closing brace,
|
||||
* it returns the entire string including the braces.
|
||||
*
|
||||
* @param open the opening character, e.g. '{'
|
||||
* @param close the closing character, e.g. '}'
|
||||
* @return a parser for the brace encloosed string.
|
||||
*/
|
||||
private[sbt] def braces(open: Char, close: Char): Parser[String] = {
|
||||
val notDelim = charClass(c => c != open && c != close).*.string
|
||||
def impl(): Parser[String] = {
|
||||
(open ~ (notDelim ~ close).?).flatMap {
|
||||
case (l, Some((content, r))) => Parser.success(l + content + r)
|
||||
case (l, None) =>
|
||||
((notDelim ~ impl()).map {
|
||||
case (leftPrefix, nestedBraces) => leftPrefix + nestedBraces
|
||||
}.+ ~ notDelim ~ close).map {
|
||||
case ((nested, suffix), r) => l + nested.mkString + suffix + r
|
||||
}
|
||||
}
|
||||
}
|
||||
impl()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single escape sequence into the represented Char.
|
||||
* Escapes start with a backslash and are followed by `u` for a [[UnicodeEscape]] or by `b`, `t`, `n`, `f`, `r`, `"`, `'`, `\` for standard escapes.
|
||||
*/
|
||||
lazy val EscapeSequence: Parser[Char] =
|
||||
BackslashChar ~> ('b' ^^^ '\b' | 't' ^^^ '\t' | 'n' ^^^ '\n' | 'f' ^^^ '\f' | 'r' ^^^ '\r' |
|
||||
'\"' ^^^ '\"' | '\'' ^^^ '\'' | '\\' ^^^ '\\' | UnicodeEscape)
|
||||
|
||||
/**
|
||||
* Parses a single unicode escape sequence into the represented Char.
|
||||
* A unicode escape begins with a backslash, followed by a `u` and 4 hexadecimal digits representing the unicode value.
|
||||
*/
|
||||
lazy val UnicodeEscape: Parser[Char] =
|
||||
("u" ~> repeat(HexDigit, 4, 4)) map { seq =>
|
||||
Integer.parseInt(seq.mkString, 16).toChar
|
||||
}
|
||||
|
||||
/** Parses an unquoted, non-empty String value that cannot start with a double quote and cannot contain whitespace.*/
|
||||
lazy val NotQuoted = (NotDQuoteSpaceClass ~ OptNotSpace) map { case (c, s) => c.toString + s }
|
||||
|
||||
/**
|
||||
* Applies `rep` zero or more times, separated by `sep`.
|
||||
* The result is the (possibly empty) sequence of results from the multiple `rep` applications. The `sep` results are discarded.
|
||||
*/
|
||||
def repsep[T](rep: Parser[T], sep: Parser[_]): Parser[Seq[T]] =
|
||||
rep1sep(rep, sep) ?? nilSeq[T]
|
||||
|
||||
/**
|
||||
* Applies `rep` one or more times, separated by `sep`.
|
||||
* The result is the non-empty sequence of results from the multiple `rep` applications. The `sep` results are discarded.
|
||||
*/
|
||||
def rep1sep[T](rep: Parser[T], sep: Parser[_]): Parser[Seq[T]] =
|
||||
(rep ~ (sep ~> rep).*).map { case (x ~ xs) => x +: xs }
|
||||
|
||||
/** Wraps the result of `p` in `Some`.*/
|
||||
def some[T](p: Parser[T]): Parser[Option[T]] = p map { v =>
|
||||
Some(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies `f` to the result of `p`, transforming any exception when evaluating
|
||||
* `f` into a parse failure with the exception `toString` as the message.
|
||||
*/
|
||||
def mapOrFail[S, T](p: Parser[S])(f: S => T): Parser[T] =
|
||||
p flatMap { s =>
|
||||
try {
|
||||
success(f(s))
|
||||
} catch { case e: Exception => failure(e.toString) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a space-delimited, possibly empty sequence of arguments.
|
||||
* The arguments may use quotes and escapes according to [[StringBasic]].
|
||||
*/
|
||||
def spaceDelimited(display: String): Parser[Seq[String]] =
|
||||
(token(Space) ~> token(StringBasic, display)).* <~ SpaceClass.*
|
||||
|
||||
/** Applies `p` and uses `true` as the result if it succeeds and turns failure into a result of `false`. */
|
||||
def flag[T](p: Parser[T]): Parser[Boolean] = (p ^^^ true) ?? false
|
||||
|
||||
/**
|
||||
* Defines a sequence parser where the parser used for each part depends on the previously parsed values.
|
||||
* `p` is applied to the (possibly empty) sequence of already parsed values to obtain the next parser to use.
|
||||
* The parsers obtained in this way are separated by `sep`, whose result is discarded and only the sequence
|
||||
* of values from the parsers returned by `p` is used for the result.
|
||||
*/
|
||||
def repeatDep[A](p: Seq[A] => Parser[A], sep: Parser[Any]): Parser[Seq[A]] = {
|
||||
def loop(acc: Seq[A]): Parser[Seq[A]] = {
|
||||
val next = (sep ~> p(acc)) flatMap { result =>
|
||||
loop(acc :+ result)
|
||||
}
|
||||
next ?? acc
|
||||
}
|
||||
p(Vector()) flatMap { first =>
|
||||
loop(Seq(first))
|
||||
}
|
||||
}
|
||||
|
||||
/** Applies String.trim to the result of `p`. */
|
||||
def trimmed(p: Parser[String]) = p map { _.trim }
|
||||
|
||||
/** Parses a URI that is valid according to the single argument java.net.URI constructor. */
|
||||
lazy val basicUri = mapOrFail(URIClass)(uri => new URI(uri))
|
||||
|
||||
/** Parses a URI that is valid according to the single argument java.net.URI constructor, using `ex` as tab completion examples. */
|
||||
def Uri(ex: Set[URI]) = basicUri examples (ex.map(_.toString))
|
||||
}
|
||||
|
||||
/** Provides standard [[Parser]] implementations. */
|
||||
object Parsers extends Parsers
|
||||
|
||||
/** Provides common [[Parser]] implementations and helper methods.*/
|
||||
object DefaultParsers extends Parsers with ParserMain {
|
||||
|
||||
/** Applies parser `p` to input `s` and returns `true` if the parse was successful. */
|
||||
def matches(p: Parser[_], s: String): Boolean =
|
||||
apply(p)(s).resultEmpty.isValid
|
||||
|
||||
/** Returns `true` if `s` parses successfully according to [[ID]].*/
|
||||
def validID(s: String): Boolean = {
|
||||
// Handwritten version of `matches(ID, s)` because validID turned up in profiling.
|
||||
def isIdChar(c: Char): Boolean = Character.isLetterOrDigit(c) || (c == '-') || (c == '_')
|
||||
@tailrec def isRestIdChar(cur: Int, s: String, length: Int): Boolean =
|
||||
if (cur < length)
|
||||
isIdChar(s.charAt(cur)) && isRestIdChar(cur + 1, s, length)
|
||||
else
|
||||
true
|
||||
|
||||
!s.isEmpty && Character.isLetter(s.charAt(0)) && isRestIdChar(1, s, s.length)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
object ProcessError {
|
||||
def apply(command: String, msgs: Seq[String], index: Int): String = {
|
||||
val (line, modIndex) = extractLine(command, index)
|
||||
val point = pointerSpace(command, modIndex)
|
||||
msgs.mkString("\n") + "\n" + line + "\n" + point + "^"
|
||||
}
|
||||
|
||||
def extractLine(s: String, i: Int): (String, Int) = {
|
||||
val notNewline = (c: Char) => c != '\n' && c != '\r'
|
||||
val left = takeRightWhile(s.substring(0, i))(notNewline)
|
||||
val right = s substring i takeWhile notNewline
|
||||
(left + right, left.length)
|
||||
}
|
||||
|
||||
def takeRightWhile(s: String)(pred: Char => Boolean): String = {
|
||||
def loop(i: Int): String =
|
||||
if (i < 0)
|
||||
s
|
||||
else if (pred(s(i)))
|
||||
loop(i - 1)
|
||||
else
|
||||
s.substring(i + 1)
|
||||
loop(s.length - 1)
|
||||
}
|
||||
|
||||
def pointerSpace(s: String, i: Int): String =
|
||||
(s take i) map { case '\t' => '\t'; case _ => ' ' } mkString ""
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util.complete
|
||||
|
||||
import sbt.internal.util.complete.DefaultParsers._
|
||||
|
||||
private[sbt] object SizeParser {
|
||||
def apply(s: String): Option[Long] = Parser.parse(s, value).toOption
|
||||
private sealed trait SizeUnit
|
||||
private case object Bytes extends SizeUnit
|
||||
private case object KiloBytes extends SizeUnit
|
||||
private case object MegaBytes extends SizeUnit
|
||||
private case object GigaBytes extends SizeUnit
|
||||
private def parseDouble(s: String): Parser[Either[Double, Long]] =
|
||||
try Parser.success(Left(java.lang.Double.valueOf(s)))
|
||||
catch { case _: NumberFormatException => Parser.failure(s"Couldn't parse $s as double.") }
|
||||
private def parseLong(s: String): Parser[Either[Double, Long]] =
|
||||
try Parser.success(Right(java.lang.Long.valueOf(s)))
|
||||
catch { case _: NumberFormatException => Parser.failure(s"Couldn't parse $s as double.") }
|
||||
private[this] val digit = charClass(_.isDigit, "digit")
|
||||
private[this] val numberParser: Parser[Either[Double, Long]] =
|
||||
(digit.+ ~ ('.'.examples() ~> digit.+).?).flatMap {
|
||||
case (leading, Some(decimalPart)) =>
|
||||
parseDouble(s"${leading.mkString}.${decimalPart.mkString}")
|
||||
case (leading, _) => parseLong(leading.mkString)
|
||||
}
|
||||
private[this] val unitParser: Parser[SizeUnit] =
|
||||
token("b" | "B" | "g" | "G" | "k" | "K" | "m" | "M").map {
|
||||
case "b" | "B" => Bytes
|
||||
case "g" | "G" => GigaBytes
|
||||
case "k" | "K" => KiloBytes
|
||||
case "m" | "M" => MegaBytes
|
||||
}
|
||||
private[this] def multiply(left: Either[Double, Long], right: Long): Long = left match {
|
||||
case Left(d) => (d * right).toLong
|
||||
case Right(l) => l * right
|
||||
}
|
||||
private[sbt] val value: Parser[Long] =
|
||||
((numberParser <~ SpaceClass
|
||||
.examples(" ", "b", "B", "g", "G", "k", "K", "m", "M")
|
||||
.*) ~ unitParser.?)
|
||||
.map {
|
||||
case (number, unit) =>
|
||||
unit match {
|
||||
case None | Some(Bytes) => multiply(number, right = 1L)
|
||||
case Some(KiloBytes) => multiply(number, right = 1024L)
|
||||
case Some(MegaBytes) => multiply(number, right = 1024L * 1024)
|
||||
case Some(GigaBytes) => multiply(number, right = 1024L * 1024 * 1024)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import Completion.{ token => ctoken, tokenDisplay }
|
||||
|
||||
sealed trait TokenCompletions {
|
||||
def hideWhen(f: Int => Boolean): TokenCompletions
|
||||
}
|
||||
|
||||
object TokenCompletions {
|
||||
private[sbt] abstract class Delegating extends TokenCompletions { outer =>
|
||||
def completions(seen: String, level: Int, delegate: Completions): Completions
|
||||
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Delegating {
|
||||
def completions(seen: String, level: Int, delegate: Completions): Completions =
|
||||
if (hide(level)) Completions.nil else outer.completions(seen, level, delegate)
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] abstract class Fixed extends TokenCompletions { outer =>
|
||||
def completions(seen: String, level: Int): Completions
|
||||
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Fixed {
|
||||
def completions(seen: String, level: Int) =
|
||||
if (hide(level)) Completions.nil else outer.completions(seen, level)
|
||||
}
|
||||
}
|
||||
|
||||
val default: TokenCompletions = mapDelegateCompletions((seen, level, c) => ctoken(seen, c.append))
|
||||
|
||||
def displayOnly(msg: String): TokenCompletions = new Fixed {
|
||||
def completions(seen: String, level: Int) = Completions.single(Completion.displayOnly(msg))
|
||||
}
|
||||
|
||||
def overrideDisplay(msg: String): TokenCompletions =
|
||||
mapDelegateCompletions((seen, level, c) => tokenDisplay(display = msg, append = c.append))
|
||||
|
||||
def fixed(f: (String, Int) => Completions): TokenCompletions = new Fixed {
|
||||
def completions(seen: String, level: Int) = f(seen, level)
|
||||
}
|
||||
|
||||
def mapDelegateCompletions(f: (String, Int, Completion) => Completion): TokenCompletions =
|
||||
new Delegating {
|
||||
def completions(seen: String, level: Int, delegate: Completions) =
|
||||
Completions(delegate.get.map(c => f(seen, level, c)))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import DefaultParsers._
|
||||
import TypeString._
|
||||
|
||||
/**
|
||||
* Basic representation of types parsed from Manifest.toString.
|
||||
* This can only represent the structure of parameterized types.
|
||||
* All other types are represented by a TypeString with an empty `args`.
|
||||
*/
|
||||
private[sbt] final class TypeString(val base: String, val args: List[TypeString]) {
|
||||
override def toString =
|
||||
if (base.startsWith(FunctionName))
|
||||
args.dropRight(1).mkString("(", ",", ")") + " => " + args.last
|
||||
else if (base.startsWith(TupleName))
|
||||
args.mkString("(", ",", ")")
|
||||
else
|
||||
cleanupTypeName(base) + (if (args.isEmpty) "" else args.mkString("[", ",", "]"))
|
||||
}
|
||||
|
||||
private[sbt] object TypeString {
|
||||
|
||||
/** Makes the string representation of a type as returned by Manifest.toString more readable.*/
|
||||
def cleanup(typeString: String): String =
|
||||
parse(typeString, typeStringParser) match {
|
||||
case Right(ts) => ts.toString
|
||||
case Left(_) => typeString
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a fully qualified type name provided by Manifest.toString more readable.
|
||||
* The argument should be just a name (like scala.Tuple2) and not a full type (like scala.Tuple2[Int,Boolean])
|
||||
*/
|
||||
def cleanupTypeName(base: String): String =
|
||||
dropPrefix(base).replace('$', '.')
|
||||
|
||||
/**
|
||||
* Removes prefixes from a fully qualified type name that are unnecessary in the presence of standard imports for an sbt setting.
|
||||
* This does not use the compiler and is therefore a conservative approximation.
|
||||
*/
|
||||
def dropPrefix(base: String): String =
|
||||
if (base.startsWith(SbtPrefix))
|
||||
base.substring(SbtPrefix.length)
|
||||
else if (base.startsWith(CollectionPrefix)) {
|
||||
val simple = base.substring(CollectionPrefix.length)
|
||||
if (ShortenCollection(simple)) simple else base
|
||||
} else if (base.startsWith(ScalaPrefix))
|
||||
base.substring(ScalaPrefix.length)
|
||||
else if (base.startsWith(JavaPrefix))
|
||||
base.substring(JavaPrefix.length)
|
||||
else
|
||||
TypeMap.getOrElse(base, base)
|
||||
|
||||
final val CollectionPrefix = "scala.collection."
|
||||
final val FunctionName = "scala.Function"
|
||||
final val TupleName = "scala.Tuple"
|
||||
final val SbtPrefix = "sbt."
|
||||
final val ScalaPrefix = "scala."
|
||||
final val JavaPrefix = "java.lang."
|
||||
/* scala.collection.X -> X */
|
||||
val ShortenCollection = Set("Seq", "List", "Set", "Map", "Iterable")
|
||||
|
||||
val TypeMap = Map(
|
||||
"java.io.File" -> "File",
|
||||
"java.net.URL" -> "URL",
|
||||
"java.net.URI" -> "URI"
|
||||
)
|
||||
|
||||
/**
|
||||
* A Parser that extracts basic structure from the string representation of a type from Manifest.toString.
|
||||
* This is rudimentary and essentially only decomposes the string into names and arguments for parameterized types.
|
||||
*/
|
||||
lazy val typeStringParser: Parser[TypeString] = {
|
||||
def isFullScalaIDChar(c: Char) = isScalaIDChar(c) || c == '.' || c == '$'
|
||||
lazy val fullScalaID =
|
||||
identifier(IDStart, charClass(isFullScalaIDChar, "Scala identifier character"))
|
||||
lazy val tpe: Parser[TypeString] =
|
||||
for (id <- fullScalaID; args <- ('[' ~> rep1sep(tpe, ',') <~ ']').?)
|
||||
yield new TypeString(id, args.toList.flatten)
|
||||
tpe
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
sealed trait UpperBound {
|
||||
|
||||
/** True if and only if the given value meets this bound.*/
|
||||
def >=(min: Int): Boolean
|
||||
|
||||
/** True if and only if this bound is one.*/
|
||||
def isOne: Boolean
|
||||
|
||||
/** True if and only if this bound is zero.*/
|
||||
def isZero: Boolean
|
||||
|
||||
/**
|
||||
* If this bound is zero or Infinite, `decrement` returns this bound.
|
||||
* Otherwise, this bound is finite and greater than zero and `decrement` returns the bound that is one less than this bound.
|
||||
*/
|
||||
def decrement: UpperBound
|
||||
|
||||
/** True if and only if this is unbounded.*/
|
||||
def isInfinite: Boolean
|
||||
|
||||
}
|
||||
|
||||
/** Represents unbounded. */
|
||||
case object Infinite extends UpperBound {
|
||||
|
||||
/** All finite numbers meet this bound. */
|
||||
def >=(min: Int) = true
|
||||
|
||||
def isOne = false
|
||||
def isZero = false
|
||||
def decrement = this
|
||||
def isInfinite = true
|
||||
|
||||
override def toString = "Infinity"
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a finite upper bound. The maximum allowed value is 'value', inclusive.
|
||||
* It must positive.
|
||||
*/
|
||||
final case class Finite(value: Int) extends UpperBound {
|
||||
assume(value >= 0, "Maximum occurrences must be nonnegative.")
|
||||
|
||||
def >=(min: Int) = value >= min
|
||||
def isOne = value == 1
|
||||
def isZero = value == 0
|
||||
def decrement = Finite(scala.math.max(0, value - 1))
|
||||
def isInfinite = false
|
||||
override def toString = value.toString
|
||||
}
|
||||
|
||||
object UpperBound {
|
||||
implicit def intToFinite(i: Int): Finite = Finite(i)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import org.scalacheck._, Gen._, Prop._
|
||||
|
||||
object DefaultParsersSpec extends Properties("DefaultParsers") {
|
||||
import DefaultParsers.{ ID, isIDChar, matches, validID }
|
||||
|
||||
property("∀ s ∈ String: validID(s) == matches(ID, s)") = forAll(
|
||||
(s: String) => validID(s) == matches(ID, s)
|
||||
)
|
||||
|
||||
property("∀ s ∈ genID: matches(ID, s)") = forAll(genID)(s => matches(ID, s))
|
||||
property("∀ s ∈ genID: validID(s)") = forAll(genID)(s => validID(s))
|
||||
|
||||
private val chars: Seq[Char] = Char.MinValue to Char.MaxValue
|
||||
private val genID: Gen[String] =
|
||||
for {
|
||||
c <- oneOf(chars filter (_.isLetter))
|
||||
cs <- listOf(oneOf(chars filter isIDChar))
|
||||
} yield (c :: cs).mkString
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
object JLineTest {
|
||||
import DefaultParsers._
|
||||
|
||||
val one = "blue" | "green" | "black"
|
||||
val two = token("color" ~> Space) ~> token(one)
|
||||
val three = token("color" ~> Space) ~> token(ID.examples("blue", "green", "black"))
|
||||
val four = token("color" ~> Space) ~> token(ID, "<color name>")
|
||||
|
||||
val num = token(NatBasic)
|
||||
val five = (num ~ token("+" | "-") ~ num) <~ token('=') flatMap {
|
||||
case a ~ "+" ~ b => token((a + b).toString)
|
||||
case a ~ "-" ~ b => token((a - b).toString)
|
||||
}
|
||||
|
||||
val parsers = Map("1" -> one, "2" -> two, "3" -> three, "4" -> four, "5" -> five)
|
||||
def main(args: Array[String]): Unit = {
|
||||
import jline.TerminalFactory
|
||||
import jline.console.ConsoleReader
|
||||
val reader = new ConsoleReader()
|
||||
TerminalFactory.get.init
|
||||
|
||||
val parser = parsers(args(0))
|
||||
JLineCompletion.installCustomCompletor(reader, parser)
|
||||
def loop(): Unit = {
|
||||
val line = reader.readLine("> ")
|
||||
if (line ne null) {
|
||||
println("Result: " + apply(parser)(line).resultEmpty)
|
||||
loop()
|
||||
}
|
||||
}
|
||||
loop()
|
||||
}
|
||||
}
|
||||
|
||||
import Parser._
|
||||
import org.scalacheck._
|
||||
|
||||
object ParserTest extends Properties("Completing Parser") {
|
||||
import Parsers._
|
||||
import DefaultParsers.matches
|
||||
|
||||
val nested = (token("a1") ~ token("b2")) ~ "c3"
|
||||
val nestedDisplay = (token("a1", "<a1>") ~ token("b2", "<b2>")) ~ "c3"
|
||||
|
||||
val spacePort = token(Space) ~> Port
|
||||
|
||||
def p[T](f: T): T = { println(f); f }
|
||||
|
||||
def checkSingle(in: String, expect: Completion)(expectDisplay: Completion = expect) =
|
||||
(("token '" + in + "'") |: checkOne(in, nested, expect)) &&
|
||||
(("display '" + in + "'") |: checkOne(in, nestedDisplay, expectDisplay))
|
||||
|
||||
def checkOne(in: String, parser: Parser[_], expect: Completion): Prop =
|
||||
completions(parser, in, 1) == Completions.single(expect)
|
||||
|
||||
def checkAll(in: String, parser: Parser[_], expect: Completions): Prop = {
|
||||
val cs = completions(parser, in, 1)
|
||||
("completions: " + cs) |: ("Expected: " + expect) |: (cs == expect: Prop)
|
||||
}
|
||||
|
||||
def checkInvalid(in: String) =
|
||||
(("token '" + in + "'") |: checkInv(in, nested)) &&
|
||||
(("display '" + in + "'") |: checkInv(in, nestedDisplay))
|
||||
|
||||
def checkInv(in: String, parser: Parser[_]): Prop = {
|
||||
val cs = completions(parser, in, 1)
|
||||
("completions: " + cs) |: (cs == Completions.nil: Prop)
|
||||
}
|
||||
|
||||
property("nested tokens a") =
|
||||
checkSingle("", Completion.token("", "a1"))(Completion.displayOnly("<a1>"))
|
||||
property("nested tokens a1") =
|
||||
checkSingle("a", Completion.token("a", "1"))(Completion.displayOnly("<a1>"))
|
||||
property("nested tokens a inv") = checkInvalid("b")
|
||||
property("nested tokens b") =
|
||||
checkSingle("a1", Completion.token("", "b2"))(Completion.displayOnly("<b2>"))
|
||||
property("nested tokens b2") =
|
||||
checkSingle("a1b", Completion.token("b", "2"))(Completion.displayOnly("<b2>"))
|
||||
property("nested tokens b inv") = checkInvalid("a1a")
|
||||
property("nested tokens c") = checkSingle("a1b2", Completion.suggestion("c3"))()
|
||||
property("nested tokens c3") = checkSingle("a1b2c", Completion.suggestion("3"))()
|
||||
property("nested tokens c inv") = checkInvalid("a1b2a")
|
||||
|
||||
property("suggest space") = checkOne("", spacePort, Completion.token("", " "))
|
||||
property("suggest port") = checkOne(" ", spacePort, Completion.displayOnly("<port>"))
|
||||
property("no suggest at end") = checkOne("asdf", "asdf", Completion.suggestion(""))
|
||||
property("no suggest at token end") = checkOne("asdf", token("asdf"), Completion.suggestion(""))
|
||||
property("empty suggest for examples") =
|
||||
checkOne("asdf", any.+.examples("asdf", "qwer"), Completion.suggestion(""))
|
||||
property("empty suggest for examples token") =
|
||||
checkOne("asdf", token(any.+.examples("asdf", "qwer")), Completion.suggestion(""))
|
||||
|
||||
val colors = Set("blue", "green", "red")
|
||||
val base = (seen: Seq[String]) => token(ID examples (colors -- seen))
|
||||
val sep = token(Space)
|
||||
val repeat = repeatDep(base, sep)
|
||||
def completionStrings(ss: Set[String]) = Completions(ss map (Completion.token("", _)))
|
||||
|
||||
property("repeatDep no suggestions for bad input") = checkInv(".", repeat)
|
||||
property("repeatDep suggest all") = checkAll("", repeat, completionStrings(colors))
|
||||
property("repeatDep suggest remaining two") = {
|
||||
val first = colors.toSeq.head
|
||||
checkAll(first + " ", repeat, completionStrings(colors - first))
|
||||
}
|
||||
property("repeatDep suggest remaining one") = {
|
||||
val take = colors.toSeq.take(2)
|
||||
checkAll(take.mkString("", " ", " "), repeat, completionStrings(colors -- take))
|
||||
}
|
||||
property("repeatDep requires at least one token") = !matches(repeat, "")
|
||||
property("repeatDep accepts one token") = matches(repeat, colors.toSeq.head)
|
||||
property("repeatDep accepts two tokens") = matches(repeat, colors.toSeq.take(2).mkString(" "))
|
||||
}
|
||||
object ParserExample {
|
||||
val ws = charClass(_.isWhitespace, "whitespace").+
|
||||
val notws = charClass(!_.isWhitespace, "not whitespace").+
|
||||
|
||||
val name = token("test")
|
||||
val options = (ws ~> token("quick" | "failed" | "new")).*
|
||||
val exampleSet = Set("am", "is", "are", "was", "were")
|
||||
val include = (ws ~> token(
|
||||
examples(notws.string, new FixedSetExamples(exampleSet), exampleSet.size, false)
|
||||
)).*
|
||||
|
||||
val t = name ~ options ~ include
|
||||
|
||||
// Get completions for some different inputs
|
||||
println(completions(t, "te", 1))
|
||||
println(completions(t, "test ", 1))
|
||||
println(completions(t, "test w", 1))
|
||||
|
||||
// Get the parsed result for different inputs
|
||||
println(apply(t)("te").resultEmpty)
|
||||
println(apply(t)("test").resultEmpty)
|
||||
println(apply(t)("test w").resultEmpty)
|
||||
println(apply(t)("test was were").resultEmpty)
|
||||
|
||||
def run(n: Int): Unit = {
|
||||
val a = 'a'.id
|
||||
val aq = a.?
|
||||
val aqn = repeat(aq, min = n, max = n)
|
||||
val an = repeat(a, min = n, max = n)
|
||||
val ann = aqn ~ an
|
||||
|
||||
def r = apply(ann)("a" * (n * 2)).resultEmpty
|
||||
println(r.isValid)
|
||||
}
|
||||
def run2(n: Int): Unit = {
|
||||
val ab = "ab".?.*
|
||||
val r = apply(ab)("a" * n).resultEmpty
|
||||
println(r)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
abstract class UnitSpec extends FlatSpec with Matchers
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import java.io.File
|
||||
import org.scalatest.Assertion
|
||||
import sbt.io.IO
|
||||
|
||||
class FileExamplesTest extends UnitSpec {
|
||||
|
||||
"listing all files in an absolute base directory" should
|
||||
"produce the entire base directory's contents" in {
|
||||
withDirectoryStructure() { ds =>
|
||||
ds.fileExamples().toList should contain theSameElementsAs (ds.allRelativizedPaths)
|
||||
}
|
||||
}
|
||||
|
||||
"listing files with a prefix that matches none" should "produce an empty list" in {
|
||||
withDirectoryStructure(withCompletionPrefix = "z") { ds =>
|
||||
ds.fileExamples().toList shouldBe empty
|
||||
}
|
||||
}
|
||||
|
||||
"listing single-character prefixed files" should "produce matching paths only" in {
|
||||
withDirectoryStructure(withCompletionPrefix = "f") { ds =>
|
||||
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
|
||||
}
|
||||
}
|
||||
|
||||
"listing directory-prefixed files" should "produce matching paths only" in {
|
||||
withDirectoryStructure(withCompletionPrefix = "far") { ds =>
|
||||
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
|
||||
}
|
||||
}
|
||||
|
||||
it should "produce sub-dir contents only when appending a file separator to the directory" in {
|
||||
withDirectoryStructure(withCompletionPrefix = "far" + File.separator) { ds =>
|
||||
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
|
||||
}
|
||||
}
|
||||
|
||||
"listing files with a sub-path prefix" should "produce matching paths only" in {
|
||||
withDirectoryStructure(withCompletionPrefix = "far" + File.separator + "ba") { ds =>
|
||||
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
|
||||
}
|
||||
}
|
||||
|
||||
"completing a full path" should "produce a list with an empty string" in {
|
||||
withDirectoryStructure(withCompletionPrefix = "bazaar") { ds =>
|
||||
ds.fileExamples().toList shouldEqual List("")
|
||||
}
|
||||
}
|
||||
|
||||
def withDirectoryStructure[A](withCompletionPrefix: String = "")(
|
||||
thunk: DirectoryStructure => Assertion
|
||||
): Assertion = {
|
||||
IO.withTemporaryDirectory { tempDir =>
|
||||
val ds = new DirectoryStructure(withCompletionPrefix)
|
||||
ds.createSampleDirStructure(tempDir)
|
||||
ds.fileExamples = new FileExamples(ds.baseDir, withCompletionPrefix)
|
||||
thunk(ds)
|
||||
}
|
||||
}
|
||||
|
||||
final class DirectoryStructure(withCompletionPrefix: String) {
|
||||
var fileExamples: FileExamples = _
|
||||
var baseDir: File = _
|
||||
var childFiles: List[File] = _
|
||||
var childDirectories: List[File] = _
|
||||
var nestedFiles: List[File] = _
|
||||
var nestedDirectories: List[File] = _
|
||||
|
||||
def allRelativizedPaths: List[String] =
|
||||
(childFiles ++ childDirectories ++ nestedFiles ++ nestedDirectories)
|
||||
.map(IO.relativize(baseDir, _).get)
|
||||
|
||||
def prefixedPathsOnly: List[String] =
|
||||
allRelativizedPaths
|
||||
.filter(_ startsWith withCompletionPrefix)
|
||||
.map(_ substring withCompletionPrefix.length)
|
||||
|
||||
def createSampleDirStructure(tempDir: File): Unit = {
|
||||
childFiles = toChildFiles(tempDir, List("foo", "bar", "bazaar"))
|
||||
childDirectories = toChildFiles(tempDir, List("moo", "far"))
|
||||
nestedFiles = toChildFiles(childDirectories(1), List("farfile1", "barfile2"))
|
||||
nestedDirectories = toChildFiles(childDirectories(1), List("fardir1", "bardir2"))
|
||||
|
||||
(childDirectories ++ nestedDirectories).map(_.mkdirs())
|
||||
(childFiles ++ nestedFiles).map(_.createNewFile())
|
||||
|
||||
baseDir = tempDir
|
||||
}
|
||||
|
||||
private def toChildFiles(baseDir: File, files: List[String]): List[File] =
|
||||
files.map(new File(baseDir, _))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
class FixedSetExamplesTest extends UnitSpec {
|
||||
|
||||
"adding a prefix" should "produce a smaller set of examples with the prefix removed" in {
|
||||
val _ = new Examples {
|
||||
fixedSetExamples.withAddedPrefix("f")() should contain theSameElementsAs
|
||||
(List("oo", "ool", "u"))
|
||||
fixedSetExamples.withAddedPrefix("fo")() should contain theSameElementsAs (List("o", "ol"))
|
||||
fixedSetExamples.withAddedPrefix("b")() should contain theSameElementsAs (List("ar"))
|
||||
}
|
||||
}
|
||||
|
||||
"without a prefix" should "produce the original set" in {
|
||||
val _ = new Examples {
|
||||
fixedSetExamples() shouldBe exampleSet
|
||||
}
|
||||
}
|
||||
|
||||
trait Examples {
|
||||
val exampleSet = List("foo", "bar", "fool", "fu")
|
||||
val fixedSetExamples = FixedSetExamples(exampleSet)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
package complete
|
||||
|
||||
import Completion._
|
||||
|
||||
class ParserWithExamplesTest extends UnitSpec {
|
||||
|
||||
"listing a limited number of completions" should
|
||||
"grab only the needed number of elements from the iterable source of examples" in {
|
||||
val _ = new ParserWithLazyExamples {
|
||||
parserWithExamples.completions(0)
|
||||
examples.size shouldEqual maxNumberOfExamples
|
||||
}
|
||||
}
|
||||
|
||||
"listing only valid completions" should
|
||||
"use the delegate parser to remove invalid examples" in {
|
||||
val _ = new ParserWithValidExamples {
|
||||
val validCompletions = Completions(
|
||||
Set(
|
||||
suggestion("blue"),
|
||||
suggestion("red")
|
||||
)
|
||||
)
|
||||
parserWithExamples.completions(0) shouldEqual validCompletions
|
||||
}
|
||||
}
|
||||
|
||||
"listing valid completions in a derived parser" should
|
||||
"produce only valid examples that start with the character of the derivation" in {
|
||||
val _ = new ParserWithValidExamples {
|
||||
val derivedCompletions = Completions(
|
||||
Set(
|
||||
suggestion("lue")
|
||||
)
|
||||
)
|
||||
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
|
||||
}
|
||||
}
|
||||
|
||||
"listing valid and invalid completions" should
|
||||
"produce the entire source of examples" in {
|
||||
val _ = new parserWithAllExamples {
|
||||
val completions = Completions(examples.map(suggestion(_)).toSet)
|
||||
parserWithExamples.completions(0) shouldEqual completions
|
||||
}
|
||||
}
|
||||
|
||||
"listing valid and invalid completions in a derived parser" should
|
||||
"produce only examples that start with the character of the derivation" in {
|
||||
val _ = new parserWithAllExamples {
|
||||
val derivedCompletions = Completions(
|
||||
Set(
|
||||
suggestion("lue"),
|
||||
suggestion("lock")
|
||||
)
|
||||
)
|
||||
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
|
||||
}
|
||||
}
|
||||
|
||||
class ParserWithLazyExamples
|
||||
extends ParserExample(
|
||||
GrowableSourceOfExamples(),
|
||||
maxNumberOfExamples = 5,
|
||||
removeInvalidExamples = false
|
||||
)
|
||||
|
||||
class ParserWithValidExamples extends ParserExample(removeInvalidExamples = true)
|
||||
|
||||
class parserWithAllExamples extends ParserExample(removeInvalidExamples = false)
|
||||
|
||||
case class ParserExample(
|
||||
examples: Iterable[String] = Set("blue", "yellow", "greeen", "block", "red"),
|
||||
maxNumberOfExamples: Int = 25,
|
||||
removeInvalidExamples: Boolean
|
||||
) {
|
||||
|
||||
import DefaultParsers._
|
||||
|
||||
val colorParser = "blue" | "green" | "black" | "red"
|
||||
val parserWithExamples: Parser[String] = new ParserWithExamples[String](
|
||||
colorParser,
|
||||
FixedSetExamples(examples),
|
||||
maxNumberOfExamples,
|
||||
removeInvalidExamples
|
||||
)
|
||||
}
|
||||
|
||||
case class GrowableSourceOfExamples() extends Iterable[String] {
|
||||
private var numberOfIteratedElements: Int = 0
|
||||
|
||||
override def iterator: Iterator[String] = {
|
||||
new Iterator[String] {
|
||||
var currentElement = 0
|
||||
|
||||
override def next(): String = {
|
||||
currentElement += 1
|
||||
numberOfIteratedElements = Math.max(currentElement, numberOfIteratedElements)
|
||||
numberOfIteratedElements.toString
|
||||
}
|
||||
|
||||
override def hasNext: Boolean = true
|
||||
}
|
||||
}
|
||||
|
||||
override def size: Int = numberOfIteratedElements
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util.complete
|
||||
|
||||
import org.scalatest.FlatSpec
|
||||
|
||||
class SizeParserSpec extends FlatSpec {
|
||||
"SizeParser" should "handle raw bytes" in {
|
||||
assert(Parser.parse(str = "123456", SizeParser.value) == Right(123456L))
|
||||
}
|
||||
it should "handle bytes" in {
|
||||
assert(Parser.parse(str = "123456b", SizeParser.value) == Right(123456L))
|
||||
assert(Parser.parse(str = "123456B", SizeParser.value) == Right(123456L))
|
||||
assert(Parser.parse(str = "123456 b", SizeParser.value) == Right(123456L))
|
||||
assert(Parser.parse(str = "123456 B", SizeParser.value) == Right(123456L))
|
||||
}
|
||||
it should "handle kilobytes" in {
|
||||
assert(Parser.parse(str = "123456k", SizeParser.value) == Right(123456L * 1024))
|
||||
assert(Parser.parse(str = "123456K", SizeParser.value) == Right(123456L * 1024))
|
||||
assert(Parser.parse(str = "123456 K", SizeParser.value) == Right(123456L * 1024))
|
||||
assert(Parser.parse(str = "123456 K", SizeParser.value) == Right(123456L * 1024))
|
||||
}
|
||||
it should "handle megabytes" in {
|
||||
assert(Parser.parse(str = "123456m", SizeParser.value) == Right(123456L * 1024 * 1024))
|
||||
assert(Parser.parse(str = "123456M", SizeParser.value) == Right(123456L * 1024 * 1024))
|
||||
assert(Parser.parse(str = "123456 M", SizeParser.value) == Right(123456L * 1024 * 1024))
|
||||
assert(Parser.parse(str = "123456 M", SizeParser.value) == Right(123456L * 1024 * 1024))
|
||||
}
|
||||
it should "handle gigabytes" in {
|
||||
assert(Parser.parse(str = "123456g", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
|
||||
assert(Parser.parse(str = "123456G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
|
||||
assert(Parser.parse(str = "123456 G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
|
||||
assert(Parser.parse(str = "123456 G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
|
||||
}
|
||||
it should "handle doubles" in {
|
||||
assert(Parser.parse(str = "1.25g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
|
||||
assert(Parser.parse(str = "1.25 g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
|
||||
assert(Parser.parse(str = "1.25 g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
|
||||
assert(Parser.parse(str = "1.25 G", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
|
||||
}
|
||||
private val expectedCompletions = Set("", "b", "B", "g", "G", "k", "K", "m", "M", " ")
|
||||
it should "have completions for long" in {
|
||||
val completions = Parser.completions(SizeParser.value, "123", level = 0).get.map(_.display)
|
||||
assert(completions == expectedCompletions)
|
||||
}
|
||||
it should "have completions for long with spaces" in {
|
||||
val completions = Parser.completions(SizeParser.value, "123", level = 0).get.map(_.display)
|
||||
assert(completions == expectedCompletions)
|
||||
}
|
||||
it should "have completions for double " in {
|
||||
val completions = Parser.completions(SizeParser.value, "1.25", level = 0).get.map(_.display)
|
||||
assert(completions == expectedCompletions)
|
||||
}
|
||||
it should "have completions for double with spaces" in {
|
||||
val completions = Parser.completions(SizeParser.value, "1.25 ", level = 0).get.map(_.display)
|
||||
assert(completions == expectedCompletions + "")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Simple Build Tool: Control Component
|
||||
Copyright 2009 Mark Harrah
|
||||
Licensed under BSD-style license (see LICENSE)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.IOException
|
||||
|
||||
object ErrorHandling {
|
||||
def translate[T](msg: => String)(f: => T) =
|
||||
try {
|
||||
f
|
||||
} catch {
|
||||
case e: IOException => throw new TranslatedIOException(msg + e.toString, e)
|
||||
case e: Exception => throw new TranslatedException(msg + e.toString, e)
|
||||
}
|
||||
|
||||
def wideConvert[T](f: => T): Either[Throwable, T] =
|
||||
try {
|
||||
Right(f)
|
||||
} catch {
|
||||
case ex @ (_: Exception | _: StackOverflowError) => Left(ex)
|
||||
case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err
|
||||
case x: Throwable => Left(x)
|
||||
}
|
||||
|
||||
def convert[T](f: => T): Either[Exception, T] =
|
||||
try {
|
||||
Right(f)
|
||||
} catch { case e: Exception => Left(e) }
|
||||
|
||||
def reducedToString(e: Throwable): String =
|
||||
if (e.getClass == classOf[RuntimeException]) {
|
||||
val msg = e.getMessage
|
||||
if (msg == null || msg.isEmpty) e.toString else msg
|
||||
} else
|
||||
e.toString
|
||||
}
|
||||
|
||||
sealed class TranslatedException private[sbt] (msg: String, cause: Throwable)
|
||||
extends RuntimeException(msg, cause) {
|
||||
override def toString = msg
|
||||
}
|
||||
|
||||
final class TranslatedIOException private[sbt] (msg: String, cause: IOException)
|
||||
extends TranslatedException(msg, cause)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
/** Defines a function to call as sbt exits.*/
|
||||
trait ExitHook {
|
||||
|
||||
/** Subclasses should implement this method, which is called when this hook is executed. */
|
||||
def runBeforeExiting(): Unit
|
||||
|
||||
}
|
||||
|
||||
object ExitHook {
|
||||
def apply(f: => Unit): ExitHook = new ExitHook { def runBeforeExiting() = f }
|
||||
}
|
||||
|
||||
object ExitHooks {
|
||||
|
||||
/** Calls each registered exit hook, trapping any exceptions so that each hook is given a chance to run. */
|
||||
def runExitHooks(exitHooks: Seq[ExitHook]): Seq[Throwable] =
|
||||
exitHooks.flatMap(hook => ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
final class MessageOnlyException(override val toString: String) extends RuntimeException(toString)
|
||||
|
||||
/**
|
||||
* A dummy exception for the top-level exception handler to know that an exception
|
||||
* has been handled, but is being passed further up to indicate general failure.
|
||||
*/
|
||||
final class AlreadyHandledException(val underlying: Throwable) extends RuntimeException
|
||||
|
||||
/**
|
||||
* A marker trait for a top-level exception handler to know that this exception
|
||||
* doesn't make sense to display.
|
||||
*/
|
||||
trait UnprintableException extends Throwable
|
||||
|
||||
/**
|
||||
* A marker trait that refines UnprintableException to indicate to a top-level exception handler
|
||||
* that the code throwing this exception has already provided feedback to the user about the error condition.
|
||||
*/
|
||||
trait FeedbackProvidedException extends UnprintableException
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import scala.sys.process.Process
|
||||
|
||||
/**
|
||||
* Manages forked processes created by sbt. Any process registered
|
||||
* with RunningProcesses can be killed with the killAll method. In
|
||||
* particular, this can be used in a signal handler to kill these
|
||||
* processes when the user inputs ctrl+c.
|
||||
*/
|
||||
private[sbt] object RunningProcesses {
|
||||
val active = ConcurrentHashMap.newKeySet[Process]
|
||||
def add(process: Process): Unit = active.synchronized {
|
||||
active.add(process)
|
||||
()
|
||||
}
|
||||
def remove(process: Process): Unit = active.synchronized {
|
||||
active.remove(process)
|
||||
()
|
||||
}
|
||||
def killAll(): Unit = active.synchronized {
|
||||
active.forEach(_.destroy())
|
||||
active.clear()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package xsbti;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface Logger {
|
||||
void error(Supplier<String> msg);
|
||||
|
||||
void warn(Supplier<String> msg);
|
||||
|
||||
void info(Supplier<String> msg);
|
||||
|
||||
void debug(Supplier<String> msg);
|
||||
|
||||
void trace(Supplier<Throwable> exception);
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package xsbti;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface Position {
|
||||
Optional<Integer> line();
|
||||
|
||||
String lineContent();
|
||||
|
||||
Optional<Integer> offset();
|
||||
|
||||
// pointer to the column position of the error/warning
|
||||
Optional<Integer> pointer();
|
||||
|
||||
Optional<String> pointerSpace();
|
||||
|
||||
Optional<String> sourcePath();
|
||||
|
||||
Optional<File> sourceFile();
|
||||
|
||||
// Default values to avoid breaking binary compatibility
|
||||
default Optional<Integer> startOffset() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<Integer> endOffset() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<Integer> startLine() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<Integer> startColumn() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<Integer> endLine() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<Integer> endColumn() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package xsbti;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface Problem {
|
||||
String category();
|
||||
|
||||
Severity severity();
|
||||
|
||||
String message();
|
||||
|
||||
Position position();
|
||||
|
||||
// Default value to avoid breaking binary compatibility
|
||||
/**
|
||||
* If present, the string shown to the user when displaying this Problem. Otherwise, the Problem
|
||||
* will be shown in an implementation-defined way based on the values of its other fields.
|
||||
*/
|
||||
default Optional<String> rendered() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue