From e8e56309a6a2f1058a2e795657ad604e38371a85 Mon Sep 17 00:00:00 2001 From: nixo Date: Tue, 18 Feb 2020 21:56:18 +0100 Subject: [PATCH] init (v1.24.3) --- .gitignore | 18 ++++++++ .travis.yml | 3 ++ AUTHORS | 11 +++++ CHANGELOG.md | 3 ++ CONTRIBUTING.md | 33 +++++++++++++++ LICENSE | 26 ++++++++++++ README.md | 41 ++++++++++++++++++ lib/src/candidate_paths.dart | 39 +++++++++++++++++ lib/src/has_permission.dart | 55 ++++++++++++++++++++++++ lib/src/is_executable.dart | 36 ++++++++++++++++ lib/src/util.dart | 21 ++++++++++ lib/src/which_impl.dart | 55 ++++++++++++++++++++++++ lib/which.dart | 32 ++++++++++++++ pubspec.yaml | 15 +++++++ test/candidate_paths_test.dart | 29 +++++++++++++ test/has_permission_test.dart | 29 +++++++++++++ test/is_executable_test.dart | 77 ++++++++++++++++++++++++++++++++++ test/test.dart | 19 +++++++++ test/util.dart | 41 ++++++++++++++++++ test/which_impl_test.dart | 58 +++++++++++++++++++++++++ test/which_test.dart | 29 +++++++++++++ tool/travis.sh | 26 ++++++++++++ 22 files changed, 696 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lib/src/candidate_paths.dart create mode 100644 lib/src/has_permission.dart create mode 100644 lib/src/is_executable.dart create mode 100644 lib/src/util.dart create mode 100644 lib/src/which_impl.dart create mode 100644 lib/which.dart create mode 100644 pubspec.yaml create mode 100644 test/candidate_paths_test.dart create mode 100644 test/has_permission_test.dart create mode 100644 test/is_executable_test.dart create mode 100644 test/test.dart create mode 100644 test/util.dart create mode 100644 test/which_impl_test.dart create mode 100644 test/which_test.dart create mode 100755 tool/travis.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3954a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Don’t commit the following directories created by pub. +build/ +packages +.buildlog + +# Or Settings +.settings +.project + +# Or the files created by dart2js. +*.dart.js +*.dart.precompiled.js +*.js_ +*.js.deps +*.js.map + +# Include when developing application packages. +pubspec.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b1279b7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: dart +script: ./tool/travis.sh +sudo: false diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..4396ab5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +# Names should be added to this file with this pattern: +# +# For individuals: +# Name +# +# For organizations: +# Organization +# +Google Inc. <*@google.com> +Sean Eagan + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f75a62f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.3 + +* Assume owner/group matches that of executable until it's possible to check. (#5) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6f5e0ea --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +Want to contribute? Great! First, read this page (including the small print at +the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. + +### File headers +All files in the project must start with the following header. + + // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file + // for details. All rights reserved. Use of this source code is governed by a + // BSD-style license that can be found in the LICENSE file. + +### The small print +Contributions made by corporations are covered by a different agreement than the +one above, the +[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..095cb70 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright 2015, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"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 COPYRIGHT +OWNER OR CONTRIBUTORS 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e102712 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +which [![pub package](http://img.shields.io/pub/v/which.svg)](https://pub.dartlang.org/packages/which) [![Build Status](https://travis-ci.org/dart-lang/which.svg?branch=master)](https://travis-ci.org/dart-lang/which) [![Coverage Status](https://coveralls.io/repos/dart-lang/which/badge.svg)](https://coveralls.io/r/dart-lang/which) +===== + +Check for and locate installed executables. Just like unix [which(1)][unix_which], except: + +* Doesn't shell out (fast). +* Cross-platform (works on windows). + +## Install + +```shell +pub global activate den +den install which +``` + +## Usage + +```dart +import 'dart:io'; + +import 'package:which/which.dart'; + +main(arguments) async { + + // Asynchronously + var git = await which('git', orElse: () => null); + + // Or synchronously + var git = whichSync('git', orElse: () => null); + + if (git == null) { + print('Please install git and try again'); + exit(1); + } + + await Process.run(git, ['add', '-A']); + await Process.run(git, ['commit', '-m', arguments.first]); +} +``` + +[unix_which]: http://en.wikipedia.org/wiki/Which_%28Unix%29 diff --git a/lib/src/candidate_paths.dart b/lib/src/candidate_paths.dart new file mode 100644 index 0000000..047ba31 --- /dev/null +++ b/lib/src/candidate_paths.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.src.candidate_paths; + +import 'dart:io'; + +import 'package:path/path.dart'; + +Iterable getCandidatePaths(String command, Map environment, bool isWindows, Context context) { + if (context.isAbsolute(command)) return [command]; + + String getEnvVar(String envVar, String defaultValue) { + var v = environment[envVar]; + return v == null ? defaultValue : v; + } + + var pathVarSeparator = isWindows ? ";" : ":"; + + List splitEnvVar(String envVar, String defaultValue) => + getEnvVar(envVar, defaultValue).split(pathVarSeparator); + + var pathEnv = splitEnvVar('PATH', ''); + + var noExtPaths = + pathEnv.map((pathEntry) => context.join(pathEntry, command)); + + if (!isWindows) return noExtPaths; + + pathEnv.insert(0, context.current); + var pathExt = splitEnvVar('PATHEXT', ".EXE"); + if (command.contains('.')) pathExt.insert(0, ''); + return noExtPaths.expand((commandPath) => + pathExt.map((pathExtEntry) => commandPath + pathExtEntry)); +} + +Iterable getRealCandidatePaths(String command) => + getCandidatePaths(command, Platform.environment, Platform.isWindows, context); diff --git a/lib/src/has_permission.dart b/lib/src/has_permission.dart new file mode 100644 index 0000000..b76eb39 --- /dev/null +++ b/lib/src/has_permission.dart @@ -0,0 +1,55 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// See http://dartbug.com/22036 +library which.src.has_permission; + +class FilePermission { + + final int index; + final String _name; + + const FilePermission._(this.index, this._name); + + static const EXECUTE = const FilePermission._(0, 'EXECUTE'); + static const WRITE = const FilePermission._(1, 'WRITE'); + static const READ = const FilePermission._(2, 'READ'); + static const SET_UID = const FilePermission._(3, 'SET_UID'); + static const SET_GID = const FilePermission._(4, 'SET_GID'); + static const STICKY = const FilePermission._(5, 'STICKY'); + + static const List values = const [EXECUTE, WRITE, READ, SET_UID, SET_GID, STICKY]; + + String toString() => 'FilePermission.$_name'; +} + +class FilePermissionRole { + + final int index; + final String _name; + + const FilePermissionRole._(this.index, this._name); + + static const WORLD = const FilePermissionRole._(0, 'WORLD'); + static const GROUP = const FilePermissionRole._(1, 'GROUP'); + static const OWNER = const FilePermissionRole._(2, 'OWNER'); + + static const List values = const [WORLD, GROUP, OWNER]; + + String toString() => 'FilePermissionRole.$_name'; +} + +bool hasPermission(int fileStatMode, FilePermission permission, {FilePermissionRole role: FilePermissionRole.WORLD}) { + var bitIndex = _getPermissionBitIndex(permission, role); + return (fileStatMode & (1 << bitIndex)) != 0; +} + +int _getPermissionBitIndex(FilePermission permission, FilePermissionRole role) { + switch (permission) { + case FilePermission.SET_UID: return 11; + case FilePermission.SET_GID: return 10; + case FilePermission.STICKY: return 9; + default: return (role.index * 3) + permission.index; + } +} diff --git a/lib/src/is_executable.dart b/lib/src/is_executable.dart new file mode 100644 index 0000000..fb189de --- /dev/null +++ b/lib/src/is_executable.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.src.is_executable; + +import 'dart:async'; +import 'dart:io'; + +import 'package:when/when.dart'; + +import 'has_permission.dart'; + +Future isExecutable(String path, bool isWindows, Future getStat(path)) => + _isExecutable(path, isWindows, getStat); + +bool isExecutableSync(String path, bool isWindows, FileStat getStat(path)) => + _isExecutable(path, isWindows, getStat); + +_isExecutable(String path, bool isWindows, getStat(path)) => + when(() => getStat(path), onSuccess: (stat) => isExecutableStat(stat, isWindows)); + +/// Tests whether the file exists and is executable. +bool isExecutableStat(FileStat stat, bool isWindows) { + if (FileSystemEntityType.FILE != stat.type) return false; + + // There is no concept of executable on windows. + if (isWindows) return true; + + // TODO: This currently produces false positives (returns true when it + // shouldn't) when the uid/gid of current user and executable don't + // match. Fix if/when uid/gid support is added: + // http://dartbug.com/22037. + return FilePermissionRole.values.any((role) => + hasPermission(stat.mode, FilePermission.EXECUTE, role: role)); +} diff --git a/lib/src/util.dart b/lib/src/util.dart new file mode 100644 index 0000000..faeda94 --- /dev/null +++ b/lib/src/util.dart @@ -0,0 +1,21 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.src.util; + +import 'dart:async'; + +/// Transparently call `firstWhere` on a [Stream] or [Iterable]. +// TODO: Remove once https://dartbug.com/22028 is fixed. +dynamic firstWhere(sequence, test, { orElse() }) => sequence is Iterable ? + sequence.firstWhere(test, orElse: orElse) : + _streamFirstWhere(sequence, test, orElse: orElse); + +Future _streamFirstWhere(Stream stream, test(item), { orElse() }) { + var pairs = stream.asyncMap((item) => test(item).then((result) => [item, result])); + return pairs.firstWhere((pair) => pair.last, defaultValue: () => [orElse(), null]).then((pair) => pair.first); +} + +/// The identity function simply returns its argument ([x]). +dynamic identity(x) => x; diff --git a/lib/src/which_impl.dart b/lib/src/which_impl.dart new file mode 100644 index 0000000..d06fdbc --- /dev/null +++ b/lib/src/which_impl.dart @@ -0,0 +1,55 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.src.which_impl; + +import 'dart:async'; + +import 'package:when/when.dart'; + +import 'util.dart'; + +Future which( + String command, + Iterable candidatePaths, + bool isWindows, + Future isExecutable(String path, bool isWindows), + orElse()) => new Future(() => _which( + command, + candidatePaths, + isWindows, + isExecutable, + orElse, + toSequence: (items) => new Stream.fromIterable(items))); + +String whichSync( + String command, + Iterable candidatePaths, + bool isWindows, + bool isExecutable(String path, bool isWindows), + orElse()) => _which( + command, + candidatePaths, + isWindows, + isExecutable, + orElse); + +_which( + String command, + Iterable candidatePaths, + bool isWindows, + isExecutable(String path, bool isWindows), + orElse(), + {toSequence(Iterable items): identity}) => when( + () => firstWhere( + toSequence(candidatePaths), + (path) => isExecutable(path, isWindows), + orElse: orElse != null ? orElse : () => _commandNotFound(command, null)), + onError: (e) => _commandNotFound(command, e)); + +_commandNotFound(String command, e) { + var message = 'Command not found: $command'; + if (e != null) message += '\n$e'; + throw new StateError(message); +} diff --git a/lib/which.dart b/lib/which.dart new file mode 100644 index 0000000..693f427 --- /dev/null +++ b/lib/which.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which; + +import 'dart:async'; +import 'dart:io'; + +import 'src/candidate_paths.dart'; +import 'src/is_executable.dart'; +import 'src/which_impl.dart' as impl; + +/// Returns a future for the first [command] executable in the `PATH`. +/// +/// If [command] is not found, [orElse] is called, which defaults to throwing. +Future which(String command, { orElse() }) => new Future(() => impl.which( + command, + getRealCandidatePaths(command), + Platform.isWindows, + (path, isWindows) => isExecutable(path, isWindows, FileStat.stat), + orElse)); + +/// Returns the first [command] executable in the `PATH`. +/// +/// If [command] is not found, [orElse] is called, which defaults to throwing. +String whichSync(String command, { orElse() }) => impl.whichSync( + command, + getRealCandidatePaths(command), + Platform.isWindows, + (path, isWindows) => isExecutableSync(path, isWindows, FileStat.statSync), + orElse); diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..55014fa --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,15 @@ +name: which +version: 0.1.3 +authors: + Dart Team + Sean Eagan +description: Like unix which(1) - check for and locate installed executables. +homepage: https://github.com/dart-lang/which +environment: + sdk: '>=1.0.0 <2.0.0' +dependencies: + path: '>=1.3.1 <2.0.0' + when: '>=0.2.0 <0.3.0' +dev_dependencies: + mockito: '>=0.8.1 <0.9.0' + unittest: '>=0.11.4 <0.12.0' diff --git a/test/candidate_paths_test.dart b/test/candidate_paths_test.dart new file mode 100644 index 0000000..fdb6fa1 --- /dev/null +++ b/test/candidate_paths_test.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.test.candidate_paths; + +import 'package:unittest/unittest.dart'; + +import 'util.dart'; + +main() { + group('getCandidatePaths', () { + test('posix', () { + var candidatePaths = getPosixCandidatePaths('z', '/x/y:/a/b/c', '/foo/bar'); + expect(candidatePaths, ['/x/y/z', '/a/b/c/z']); + }); + + test('windows', () { + var candidatePaths = getWindowsCandidatePaths('z', r'C:\x\y;C:\a\b\c', '.EXE;.BAT', r'C:\foo\bar'); + expect(candidatePaths, [ + r'C:\foo\bar\z.EXE', + r'C:\foo\bar\z.BAT', + r'C:\x\y\z.EXE', + r'C:\x\y\z.BAT', + r'C:\a\b\c\z.EXE', + r'C:\a\b\c\z.BAT']); + }); + }); +} diff --git a/test/has_permission_test.dart b/test/has_permission_test.dart new file mode 100644 index 0000000..383e13e --- /dev/null +++ b/test/has_permission_test.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.test.has_permission; + +import 'package:unittest/unittest.dart'; +import 'package:which/src/has_permission.dart'; + +import 'util.dart'; + +main() { + test('hasPermission', () { + var mode = parseBinary('010101010101'); + + expect(hasPermission(mode, FilePermission.SET_UID), isFalse); + expect(hasPermission(mode, FilePermission.SET_GID), isTrue); + expect(hasPermission(mode, FilePermission.STICKY), isFalse); + expect(hasPermission(mode, FilePermission.READ, role: FilePermissionRole.OWNER), isTrue); + expect(hasPermission(mode, FilePermission.WRITE, role: FilePermissionRole.OWNER), isFalse); + expect(hasPermission(mode, FilePermission.EXECUTE, role: FilePermissionRole.OWNER), isTrue); + expect(hasPermission(mode, FilePermission.READ, role: FilePermissionRole.GROUP), isFalse); + expect(hasPermission(mode, FilePermission.WRITE, role: FilePermissionRole.GROUP), isTrue); + expect(hasPermission(mode, FilePermission.EXECUTE, role: FilePermissionRole.GROUP), isFalse); + expect(hasPermission(mode, FilePermission.READ, role: FilePermissionRole.WORLD), isTrue); + expect(hasPermission(mode, FilePermission.WRITE, role: FilePermissionRole.WORLD), isFalse); + expect(hasPermission(mode, FilePermission.EXECUTE, role: FilePermissionRole.WORLD), isTrue); + }); +} diff --git a/test/is_executable_test.dart b/test/is_executable_test.dart new file mode 100644 index 0000000..5fbd6a2 --- /dev/null +++ b/test/is_executable_test.dart @@ -0,0 +1,77 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.test.is_executable; + +import 'dart:io'; + +import 'package:mockito/mockito.dart'; +import 'package:unittest/unittest.dart'; +import 'package:which/src/is_executable.dart'; + +import 'util.dart'; + +main() { + group('isExecutableStat', () { + test('false if not a file', () { + + var stat = new MockFileStat(); + + // A directory. + when(stat.type).thenReturn(FileSystemEntityType.DIRECTORY); + + var result = isExecutableStat(stat, false); + + expect(result, isFalse); + + verifyNever(stat.mode); + }); + + test('true for all files on windows', () { + + var stat = new MockFileStat(); + + // A file. + when(stat.type).thenReturn(FileSystemEntityType.FILE); + + var result = isExecutableStat(stat, true); + + expect(result, isTrue); + + verifyNever(stat.mode); + }); + + test('true if has world execute permission', () { + var result = isExecutableStat(_getMockFileStat('000000000001'), false); + expect(result, isTrue); + }); + + test('true if has group execute permission', () { + var result = isExecutableStat(_getMockFileStat('000000001000'), false); + expect(result, isTrue); + }); + + test('true if has owner execute permission', () { + var result = isExecutableStat(_getMockFileStat('000001000000'), false); + expect(result, isTrue); + }); + + test('false if has no execute permissions', () { + var result = isExecutableStat(_getMockFileStat('111110110110'), false); + expect(result, isFalse); + }); + }); +} + +MockFileStat _getMockFileStat(String mode) { + var stat = new MockFileStat(); + + // A file. + when(stat.type).thenReturn(FileSystemEntityType.FILE); + + // Last bit is world execute. + when(stat.mode).thenReturn(int.parse(mode, radix: 2)); + + return stat; +} diff --git a/test/test.dart b/test/test.dart new file mode 100644 index 0000000..09e16ee --- /dev/null +++ b/test/test.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.test; + +import 'candidate_paths_test.dart' as candidate_paths; +import 'has_permission_test.dart' as has_permission; +import 'is_executable_test.dart' as is_exe; +import 'which_test.dart' as which; +import 'which_impl_test.dart' as which_impl; + +main() { + candidate_paths.main(); + has_permission.main(); + is_exe.main(); + which.main(); + which_impl.main(); +} diff --git a/test/util.dart b/test/util.dart new file mode 100644 index 0000000..2818d71 --- /dev/null +++ b/test/util.dart @@ -0,0 +1,41 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.test.util; + +import 'dart:io'; + +import 'package:mockito/mockito.dart'; +import 'package:path/path.dart' as path; +import 'package:which/src/candidate_paths.dart'; + +getPosixCandidatePaths(String command, String pathVar, String current) { + var env = { + 'PATH': pathVar + }; + var isWindows = false; + var context = new path.Context(style: path.Style.posix, current: current); + + return getCandidatePaths(command, env, isWindows, context); +} + +getWindowsCandidatePaths(String command, String pathVar, String pathExtVar, String current) { + var env = { + 'PATH': pathVar, + 'PATHEXT': pathExtVar + }; + var isWindows = true; + var context = new path.Context(style: path.Style.windows, current: current); + + return getCandidatePaths(command, env, isWindows, context); +} + +class MockFileStat extends Mock implements FileStat { + + MockFileStat(); + + noSuchMethod(i) => super.noSuchMethod(i); +} + +int parseBinary(String b) => int.parse(b, radix: 2); diff --git a/test/which_impl_test.dart b/test/which_impl_test.dart new file mode 100644 index 0000000..7dc9a72 --- /dev/null +++ b/test/which_impl_test.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.test.which_impl; + +import 'dart:async'; + +import 'package:unittest/unittest.dart'; +import 'package:which/src/which_impl.dart'; + +import 'util.dart'; + +main() { + group('which', () { + test('should complete to the first matching executable in candidate paths', () { + var candidatePaths = getPosixCandidatePaths('z', '/x/y:/a/b/c', '/foo/bar'); + + return which('z', candidatePaths, false, (path, isWindows) => new Future.value(path == '/x/y/z' || path == '/a/b/c/z'), null) + .then((path) => expect(path, '/x/y/z')); + }); + + test('should call orElse if command not found', () { + var candidatePaths = getPosixCandidatePaths('z', '/x/y:/a/b/c', '/foo/bar'); + + return which('z', candidatePaths, false, (path, isWindows) => new Future.value(false), () => '/or/else') + .then((path) => expect(path, '/or/else')); + }); + + test('should throw state error if command not found and orElse not provided', () { + var future = new Future(() => + which('z', [], false, (path, isWindows) => new Future.value(false), null)); + + expect(future, throwsStateError); + }); +}); + + group('whichSync', () { + test('should return the first matching executable in candidate paths', () { + var candidatePaths = getWindowsCandidatePaths('z', r'C:\x\y;C:\a\b\c', '.EXE;.BAT', r'C:\foo\bar'); + + var result = whichSync('find', candidatePaths, true, (path, isWindows) => path == r'C:\x\y\z.BAT' || path == r'C:\a\b\c\z.BAT', null); + + expect(result, r'C:\x\y\z.BAT'); + }); + + test('should call orElse if command not found', () { + var candidatePaths = getWindowsCandidatePaths('z', r'C:\x\y;C:\a\b\c', '.EXE;.BAT', r'C:\foo\bar'); + + var result = whichSync('find', candidatePaths, true, (path, isWindows) => false, () => r'C:\or\else'); + + expect(result, r'C:\or\else'); + }); + test('should throw state error if command not found and orElse not provided', () { + expect(() => whichSync('z', [], true, (path, isWindows) => false, null), throwsStateError); + }); + }); +} diff --git a/test/which_test.dart b/test/which_test.dart new file mode 100644 index 0000000..b3c21f6 --- /dev/null +++ b/test/which_test.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library which.test.which; + +import 'package:path/path.dart'; +import 'package:unittest/unittest.dart'; +import 'package:which/which.dart'; + +main() { + group('which', () { + // Any dart:io supported platform (*nix, osx, windows) should have `find`. + test('should find `find`', () => which('find').then(_testResult)); + }); + + group('whichSync', () { + // Any dart:io supported platform (*nix, osx, windows) should have `find`. + test('should find `find`', () { + _testResult(whichSync('find')); + }); + }); +} + +_testResult(String path) { + expect(path, isNotNull); + var base = basenameWithoutExtension(path); + expect(base, 'find'); +} diff --git a/tool/travis.sh b/tool/travis.sh new file mode 100755 index 0000000..f534076 --- /dev/null +++ b/tool/travis.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +# Fast fail the script on failures. +set -e + +# Verify that the libraries are error free. +dartanalyzer --fatal-warnings \ + lib/which.dart \ + test/test.dart + +# Run the tests. +dart test/test.dart + +# Install dart_coveralls; gather and send coverage data. +if [ "$COVERALLS_TOKEN" ]; then + pub global activate dart_coveralls 0.1.11 + pub global run dart_coveralls report \ + --token $COVERALLS_TOKEN \ + --retry 2 \ + --exclude-test-files \ + test/test.dart +fi