├── deploy.cmd
├── .nuget
├── NuGet.exe
├── NuGet.Config
└── NuGet.targets
├── debug.psc1
├── Jump.Location
├── debug.psc1
├── LocationNotFoundException.cs
├── TabExpansion.ps1
├── Types.ps1xml
├── JumpLocationSnapIn.cs
├── DirectoryWaitPeriod.cs
├── Jump.Location.Format.ps1xml
├── Load.ps1
├── Database.cs
├── Properties
│ └── AssemblyInfo.cs
├── Install.ps1
├── Record.cs
├── Jump.Location.psd1
├── SetJumpLocationCommand.cs
├── GetJumpStatusCommand.cs
├── Jump.Location.csproj
├── FileStoreProvider.cs
└── CommandController.cs
├── publish.ps1
├── Jump.Location.Specs
├── packages.config
├── DirectoryWaitPeriodSpec.cs
├── RecordSpec.cs
├── Properties
│ └── AssemblyInfo.cs
├── DatabaseSpec.cs
├── Jump.Location.Specs.csproj
├── FileStoreProviderSpec.cs
└── CommandControllerSpec.cs
├── Chocolatey
├── tools
│ └── chocolateyInstall.ps1
└── Jump-Location.nuspec
├── .travis.yml
├── .gitattributes
├── copyBuild.ps1
├── License.md
├── Jump.Location.sln
├── .gitignore
└── Readme.md
/deploy.cmd:
--------------------------------------------------------------------------------
1 | cp .\Build\* %USERPROFILE%\Documents\WindowsPowerShell\Modules\Jump-location
--------------------------------------------------------------------------------
/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkellogg/Jump-Location/HEAD/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/debug.psc1:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2.0
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Jump.Location/debug.psc1:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2.0
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/publish.ps1:
--------------------------------------------------------------------------------
1 | $version = "0.6.0"
2 | # zip and push binaries to SourceForge first...
3 |
4 | .\.nuget\nuget pack "Chocolatey\Jump-Location.nuspec" -Version $version -OutputDirectory Chocolatey
5 | # now upload .nupkg to Chocolatey.org...
6 | # ?
7 | # profit!
--------------------------------------------------------------------------------
/Jump.Location.Specs/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Chocolatey/tools/chocolateyInstall.ps1:
--------------------------------------------------------------------------------
1 | $name = "Jump-Location"
2 | $url = "http://sourceforge.net/projects/jumplocation/files/Jump-Location-0.6.0.zip/download"
3 | $unzipLocation = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
4 |
5 | Install-ChocolateyZipPackage $name $url $unzipLocation
6 |
7 | $installer = Join-Path $unziplocation 'Install.ps1'
8 | & $installer
--------------------------------------------------------------------------------
/Jump.Location/LocationNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Jump.Location
4 | {
5 | public class LocationNotFoundException : Exception
6 | {
7 | public LocationNotFoundException(string query)
8 | :base(string.Format("Could not find find a suitable match for search query '{0}' in database", query))
9 | {
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | solution: Jump.Location.sln
3 | install:
4 | - sudo apt-get install -y powershell
5 | - nuget restore Jump.Location.sln
6 | - nuget install xunit.runners -Version 1.9.2 -OutputDirectory testrunner
7 | script:
8 | - xbuild /p:Configuration=Release Jump.Location.sln
9 | - mono ./testrunner/xunit.runners.1.9.2/tools/xunit.console.clr4.exe ./Jump.Location.Specs/bin/Release/Jump.Location.Specs.dll
10 |
--------------------------------------------------------------------------------
/Jump.Location/TabExpansion.ps1:
--------------------------------------------------------------------------------
1 |
2 | if (Test-Path Function:\TabExpansion) {
3 | Rename-Item Function:\TabExpansion PreJumpTabExpansion
4 | }
5 |
6 | function global:TabExpansion($line, $lastWord) {
7 | switch -regex ($line) {
8 | "^(Set-JumpLocation|j|xj) .*" {
9 | [Jump.Location.SetJumpLocationCommand]::GetTabExpansion($line, $lastWord)
10 | }
11 | default {
12 | if (Test-Path Function:\PreJumpTabExpansion) {
13 | PreJumpTabExpansion $line $lastWord
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/Jump.Location/Types.ps1xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jump.Location.IRecord
5 |
6 |
7 | PSStandardMembers
8 |
9 |
10 | DefaultDisplayPropertySet
11 |
12 | Weight
13 | Path
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Jump.Location/JumpLocationSnapIn.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Management.Automation;
3 |
4 | namespace Jump.Location
5 | {
6 | [RunInstaller(true)]
7 | public class JumpLocationSnapIn : PSSnapIn
8 | {
9 | public override string Name
10 | {
11 | get { return "Jump-Location"; }
12 | }
13 |
14 | public override string Vendor
15 | {
16 | get { return "Tim Kellogg"; }
17 | }
18 |
19 | public override string Description
20 | {
21 | get { return "Like Set-Location but reads your mind"; }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/copyBuild.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | copy files from bin folder to Build folder
4 | #>
5 |
6 | if (-not $(Test-Path Build)) {
7 | $null = mkdir Build
8 | }
9 |
10 | $source = "Jump.Location\bin\Debug"
11 | $destination = "Build"
12 |
13 | Write-Host "Copy bits from $source to $destination"
14 |
15 | cp Readme.md $destination
16 | cp License.md $destination
17 |
18 | cp $source\Jump.Location.dll $destination
19 | cp $source\Jump.Location.psd1 $destination
20 | cp $source\Jump.Location.Format.ps1xml $destination
21 | cp $source\Types.ps1xml $destination
22 | cp $source\Install.ps1 $destination
23 | cp $source\Load.ps1 $destination
24 | cp $source\TabExpansion.ps1 $destination
25 |
--------------------------------------------------------------------------------
/Jump.Location/DirectoryWaitPeriod.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Jump.Location
4 | {
5 | class DirectoryWaitPeriod
6 | {
7 | private readonly IRecord record;
8 | private readonly DateTime startTime;
9 | private bool hasDestroyed;
10 |
11 | public DirectoryWaitPeriod(IRecord record, DateTime now)
12 | {
13 | this.record = record;
14 | startTime = now;
15 | }
16 |
17 | public void CloseAndUpdate()
18 | {
19 | if (hasDestroyed)
20 | {
21 | // ignore, don't throw error messages.
22 | return;
23 | }
24 |
25 | record.AddTimeSpent(DateTime.Now - startTime);
26 | hasDestroyed = true;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Jump.Location.Specs/DirectoryWaitPeriodSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Moq;
3 | using Xunit;
4 | using Should;
5 |
6 | namespace Jump.Location.Specs
7 | {
8 | public class DirectoryWaitPeriodSpec
9 | {
10 | [Fact]
11 | public void It_updates_the_records_weight_upon_CloseAndUpdate()
12 | {
13 | var now = DateTime.Now.AddMinutes(-3);
14 | var recordMock = new Mock();
15 | var waitPeriod = new DirectoryWaitPeriod(recordMock.Object, now);
16 |
17 | waitPeriod.CloseAndUpdate();
18 | recordMock.Verify(x => x.AddTimeSpent(It.IsAny()));
19 | }
20 |
21 | [Fact]
22 | public void You_cant_call_CloseAndUpdate_twice()
23 | {
24 | var waitPeriod = new DirectoryWaitPeriod(Mock.Of(), DateTime.Now);
25 | waitPeriod.CloseAndUpdate();
26 | Assert.Throws(() => waitPeriod.CloseAndUpdate());
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Tim Kellogg
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/Jump.Location/Jump.Location.Format.ps1xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jump.Location.Record
6 |
7 | Jump.Location.IRecord
8 | Jump.Location.Record
9 |
10 |
11 |
12 |
13 | 15
14 |
15 |
16 | 75
17 |
18 |
19 |
20 |
21 |
22 |
23 | Weight
24 |
25 |
26 | Path
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Chocolatey/Jump-Location.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jump-Location
5 | 0.6.0
6 | tkellogg
7 | LCHarold
8 | https://github.com/tkellogg/Jump-Location/blob/master/License.md
9 | https://github.com/tkellogg/Jump-Location
10 | false
11 | Jump-Location is a faster way to navigate your filesystem. It works by maintaining a database of the directories you use the most from the command line.
12 |
13 | Directories must be visited first before they can be jumped to.
14 | A faster way to navigate your filesystem.
15 | Jump-Location is a PowerShell implementation of the *nix autojump command.
16 | jump location jump-location powershell ps autojump
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Jump.Location/Load.ps1:
--------------------------------------------------------------------------------
1 | # Install.ps1
2 | #
3 | # 1. Install Jump-Location
4 | # 2. Setup alias j
5 | # 3. Setup jumpstat alias (function)
6 | # 4. Start watching directory changes
7 | # 5. Register tab expansion
8 |
9 | $fullpath = Split-Path -Parent $MyInvocation.MyCommand.Path
10 | $dllpath = $fullpath + "\Jump.Location.dll"
11 |
12 | if (-Not (Get-Command Jump-Location -ErrorAction SilentlyContinue)) {
13 |
14 | Import-Module $dllpath -Global -DisableNameChecking
15 | New-Alias -Name j -Value Set-JumpLocation -Scope Global
16 |
17 | # this alias is for backward compatability
18 | New-Alias -Name Jump-Location -Value Set-JumpLocation -Scope Global
19 |
20 | New-Alias -Name jumpstat -Value Get-JumpStatus -Scope Global
21 |
22 | function global:getj {
23 | Param (
24 | [Parameter(ValueFromRemainingArguments=$true)] $args
25 | )
26 | jumpstat -First @args
27 | }
28 |
29 | function global:xj {
30 | Param (
31 | [Parameter(ValueFromRemainingArguments=$true)] $args
32 | )
33 | explorer $(jumpstat -First @args)
34 | }
35 |
36 | Set-JumpLocation -Initialize
37 |
38 | . $($fullpath + "\TabExpansion.ps1")
39 |
40 | } else {
41 | Write-Warning "Jump-Location already loaded"
42 | }
43 |
--------------------------------------------------------------------------------
/Jump.Location/Database.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Jump.Location
5 | {
6 | public interface IDatabase
7 | {
8 | IEnumerable Records { get; }
9 | void Add(string fullPath);
10 | void Add(IRecord record);
11 | bool Remove(IRecord record);
12 | IRecord GetByFullName(string fullName);
13 | }
14 |
15 | class Database : IDatabase
16 | {
17 | readonly List records = new List();
18 |
19 | public IEnumerable Records { get { return records; } }
20 |
21 | public void Add(string fullPath)
22 | {
23 | records.Add(new Record(fullPath));
24 | }
25 |
26 | public void Add(IRecord record)
27 | {
28 | records.Add(record);
29 | }
30 |
31 | public bool Remove(IRecord record)
32 | {
33 | return records.Remove(record);
34 | }
35 |
36 | public IRecord GetByFullName(string fullName)
37 | {
38 | var record = records.FirstOrDefault(x => x.FullName == fullName);
39 |
40 | if (record == null)
41 | {
42 | record = new Record(fullName);
43 | Add(record);
44 | }
45 |
46 | return record;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Jump.Location.Specs/RecordSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Should;
3 | using Xunit;
4 | using Xunit.Extensions;
5 |
6 | namespace Jump.Location.Specs
7 | {
8 | public class RecordSpec
9 | {
10 | public class DescribeFullName
11 | {
12 | [Fact]
13 | public void It_splits_the_supplied_path_on_the_cons()
14 | {
15 | var record = new Record("provider::path", 0);
16 | record.Provider.ShouldEqual("provider");
17 | record.Path.ShouldEqual("path");
18 | }
19 |
20 | [Theory]
21 | [InlineData("C:\\debug\\")]
22 | [InlineData("")]
23 | [InlineData("::")]
24 | [InlineData("::C:\\debug")]
25 | [InlineData("C:\\debug::")]
26 | public void It_throws_ArgumentException_when_path_is_invalid(string path)
27 | {
28 | Assert.Throws(() => new Record(path, 0));
29 | }
30 | }
31 |
32 | public class DescribePathSegments
33 | {
34 | [Fact]
35 | public void It_splits_Path_into_segments_separated_by_backslash_and_lowercased()
36 | {
37 | var record = new Record(@"Provider::C:\Program Files (x86)\Alteryx\Engine");
38 | record.PathSegments.ShouldEqual(new[] {"c:", "program files (x86)", "alteryx", "engine"});
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Jump.Location.Specs/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Jump.Location.Specs")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Hewlett-Packard")]
12 | [assembly: AssemblyProduct("Jump.Location.Specs")]
13 | [assembly: AssemblyCopyright("Copyright © Hewlett-Packard 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("360fefbe-2766-4418-8097-bb13a2ee7a1f")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Jump.Location/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Jump.Location")]
9 | [assembly: AssemblyDescription("Powershell Cmdlet to jump around")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Tim Kellogg")]
12 | [assembly: AssemblyProduct("Jump.Location")]
13 | [assembly: AssemblyCopyright("Copyright © Tim Kellogg 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("f0e2fb34-f28f-4fd9-bbf2-0e58ec55b429")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("0.6.0.0")]
36 | [assembly: AssemblyFileVersion("0.6.0.0")]
37 | [assembly: InternalsVisibleTo("Jump.Location.Specs")]
38 |
--------------------------------------------------------------------------------
/Jump.Location.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jump.Location", "Jump.Location\Jump.Location.csproj", "{813430AC-0181-4ACB-B731-C5F6578CEC64}"
5 | EndProject
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jump.Location.Specs", "Jump.Location.Specs\Jump.Location.Specs.csproj", "{C0A231D4-4E62-4E22-BD81-EB8257B15D21}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{4B6F34CE-160F-4857-82B2-6369A41AA07B}"
9 | ProjectSection(SolutionItems) = preProject
10 | .nuget\NuGet.exe = .nuget\NuGet.exe
11 | .nuget\NuGet.targets = .nuget\NuGet.targets
12 | EndProjectSection
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {813430AC-0181-4ACB-B731-C5F6578CEC64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {813430AC-0181-4ACB-B731-C5F6578CEC64}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {813430AC-0181-4ACB-B731-C5F6578CEC64}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {813430AC-0181-4ACB-B731-C5F6578CEC64}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {C0A231D4-4E62-4E22-BD81-EB8257B15D21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {C0A231D4-4E62-4E22-BD81-EB8257B15D21}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {C0A231D4-4E62-4E22-BD81-EB8257B15D21}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {C0A231D4-4E62-4E22-BD81-EB8257B15D21}.Release|Any CPU.Build.0 = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(SolutionProperties) = preSolution
30 | HideSolutionNode = FALSE
31 | EndGlobalSection
32 | EndGlobal
33 |
--------------------------------------------------------------------------------
/Jump.Location/Install.ps1:
--------------------------------------------------------------------------------
1 | # Adapted from posh-git's install.ps1
2 | # Copyright (c) 2010-2011 Keith Dahlby and contributors
3 |
4 | param(
5 | [switch]$WhatIf = $false,
6 | [string] $WhichProfile = $PROFILE.CurrentUserCurrentHost
7 | )
8 |
9 | if($PSVersionTable.PSVersion.Major -lt 3) {
10 | Write-Warning "Jump-Location requires PowerShell 3.0 or better; you have version $($Host.Version)."
11 | return
12 | }
13 |
14 | if(!(Test-Path $WhichProfile)) {
15 | Write-Host "Creating PowerShell profile...`n$WhichProfile"
16 | New-Item $WhichProfile -Force -Type File -ErrorAction Stop -WhatIf:$WhatIf > $null
17 | }
18 |
19 | $installDir = Split-Path $MyInvocation.MyCommand.Path -Parent
20 |
21 | # Adapted from http://www.west-wind.com/Weblog/posts/197245.aspx
22 | function Get-FileEncoding($Path) {
23 | $bytes = [byte[]](Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4)
24 |
25 | if(!$bytes) { return 'utf8' }
26 |
27 | switch -regex ('{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0],$bytes[1],$bytes[2],$bytes[3]) {
28 | '^efbbbf' { return 'utf8' }
29 | '^2b2f76' { return 'utf7' }
30 | '^fffe' { return 'unicode' }
31 | '^feff' { return 'bigendianunicode' }
32 | '^0000feff' { return 'utf32' }
33 | default { return 'ascii' }
34 | }
35 | }
36 |
37 | $profileLine = "Import-Module '$installDir\Jump.Location.psd1'"
38 | if(Select-String -Path $WhichProfile -Pattern $profileLine -Quiet -SimpleMatch) {
39 | Write-Host "It seems Jump-Location is already installed..."
40 | return
41 | }
42 |
43 | Write-Host "Adding Jump-Location to profile..."
44 | @"
45 |
46 | # Load Jump-Location profile
47 | $profileLine
48 |
49 | "@ | Out-File $WhichProfile -Append -WhatIf:$WhatIf -Encoding (Get-FileEncoding $WhichProfile)
50 |
51 | Write-Host 'Jump-Location sucessfully installed!'
52 | Write-Host 'Please reload your profile for the changes to take effect:'
53 | Write-Host " . $WhichProfile"
--------------------------------------------------------------------------------
/Jump.Location/Record.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace Jump.Location
5 | {
6 | public interface IRecord
7 | {
8 | string Provider { get; }
9 | string Path { get; }
10 | string FullName { get; set; }
11 | decimal Weight { get; }
12 | string[] PathSegments { get; }
13 | void AddTimeSpent(TimeSpan timeSpan);
14 | }
15 |
16 | public class Record : IRecord
17 | {
18 | private string[] pathSegments;
19 |
20 | public Record(string fullName, decimal weight)
21 | {
22 | FullName = fullName;
23 | Weight = weight;
24 | }
25 |
26 | public Record(string fullName)
27 | :this(fullName, 0)
28 | {
29 | }
30 |
31 | public string Provider { get; private set; }
32 | public string Path { get; private set; }
33 |
34 | public string FullName
35 | {
36 | get { return string.Format("{0}::{1}", Provider, Path); }
37 | set
38 | {
39 | var parts = value.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries);
40 |
41 | if (parts == null || parts.Length != 2)
42 | throw new ArgumentException("Expected FullName in format of 'Provider::Path' but got " + value);
43 |
44 | Provider = parts[0];
45 | Path = parts[1];
46 | }
47 | }
48 |
49 | public decimal Weight { get; set; }
50 |
51 | public string[] PathSegments
52 | {
53 | get { return pathSegments ?? (pathSegments = GetPathSegments()); }
54 | }
55 |
56 | private string[] GetPathSegments()
57 | {
58 | return Path.Split('\\').Select(x => x.ToLower()).ToArray();
59 | }
60 |
61 | public void AddTimeSpent(TimeSpan timeSpan)
62 | {
63 | Weight += (decimal) timeSpan.TotalSeconds;
64 | }
65 |
66 | public override string ToString()
67 | {
68 | return Path;
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/Jump.Location.Specs/DatabaseSpec.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Moq;
3 | using Should;
4 | using Xunit;
5 |
6 | namespace Jump.Location.Specs
7 | {
8 | public class DatabaseSpec
9 | {
10 | public class DescribeAdding
11 | {
12 | [Fact]
13 | public void It_adds_a_new_record_by_path()
14 | {
15 | var db = new Database();
16 | db.Add("full::path");
17 | db.Records.Count().ShouldEqual(1);
18 | }
19 |
20 | [Fact]
21 | public void It_adds_a_new_record_by_object()
22 | {
23 | var db = new Database();
24 | db.Add(Mock.Of());
25 | db.Records.Count().ShouldEqual(1);
26 | }
27 | }
28 |
29 | public class DescribeFindingByFullName
30 | {
31 | [Fact]
32 | public void It_returns_the_matching_record()
33 | {
34 | var db = new Database();
35 | db.Add(Mock.Of(x => x.FullName == "foo"));
36 | db.Add(Mock.Of(x => x.FullName == "bar"));
37 |
38 | db.GetByFullName("bar").ShouldNotBeNull();
39 | }
40 |
41 | [Fact]
42 | public void It_creates_a_record_when_missing()
43 | {
44 | var db = new Database();
45 | db.Add(Mock.Of(x => x.FullName == "foo"));
46 | db.Add(Mock.Of(x => x.FullName == "bar"));
47 |
48 | var baz = db.GetByFullName("foo::bar");
49 | baz.ShouldNotBeNull();
50 | baz.ShouldBeType();
51 | }
52 |
53 | [Fact]
54 | public void It_adds_the_missing_record_to_its_list()
55 | {
56 | var db = new Database();
57 | db.Add(Mock.Of(x => x.FullName == "foo"));
58 | db.Add(Mock.Of(x => x.FullName == "bar"));
59 |
60 | db.GetByFullName("foo::bar");
61 | db.Records.Count().ShouldEqual(3);
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Jump.Location/Jump.Location.psd1:
--------------------------------------------------------------------------------
1 | #
2 | # Module manifest for module 'Jump.Location'
3 | #
4 | # Generated by: Tim Kellogg
5 | #
6 | # Generated on: 8/12/2012
7 | #
8 |
9 | @{
10 |
11 | # Version number of this module.
12 | ModuleVersion = '0.6.0'
13 |
14 | # ID used to uniquely identify this module
15 | GUID = '80410ccb-8093-42a4-b38e-94deef91346e'
16 |
17 | # Author of this module
18 | Author = 'Tim Kellogg'
19 |
20 | # Company or vendor of this module
21 | CompanyName = 'Unknown'
22 |
23 | # Copyright statement for this module
24 | Copyright = '2012 Tim Kellogg'
25 |
26 | # Description of the functionality provided by this module
27 | Description = 'A cd command that learns'
28 |
29 | # Script module or binary module file associated with this manifest
30 | RootModule = 'Load.ps1'
31 |
32 | # Minimum version of the Windows PowerShell engine required by this module
33 | PowerShellVersion = ''
34 |
35 | # Name of the Windows PowerShell host required by this module
36 | PowerShellHostName = ''
37 |
38 | # Minimum version of the Windows PowerShell host required by this module
39 | PowerShellHostVersion = ''
40 |
41 | # Minimum version of the .NET Framework required by this module
42 | DotNetFrameworkVersion = ''
43 |
44 | # Minimum version of the common language runtime (CLR) required by this module
45 | CLRVersion = ''
46 |
47 | # Processor architecture (None, X86, Amd64, IA64) required by this module
48 | ProcessorArchitecture = ''
49 |
50 | # Modules that must be imported into the global environment prior to importing this module
51 | RequiredModules = @()
52 |
53 | # Assemblies that must be loaded prior to importing this module
54 | RequiredAssemblies = @()
55 |
56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module
57 | ScriptsToProcess = @()
58 |
59 | # Type files (.ps1xml) to be loaded when importing this module
60 | TypesToProcess = 'Types.ps1xml'
61 |
62 | # Format files (.ps1xml) to be loaded when importing this module
63 | FormatsToProcess = 'Jump.Location.Format.ps1xml'
64 |
65 | # Modules to import as nested modules of the module specified in ModuleToProcess
66 | NestedModules = @()
67 |
68 | # Functions to export from this module
69 | FunctionsToExport = '*'
70 |
71 | # Cmdlets to export from this module
72 | CmdletsToExport = '*'
73 |
74 | # Variables to export from this module
75 | VariablesToExport = '*'
76 |
77 | # Aliases to export from this module
78 | AliasesToExport = '*'
79 |
80 | # List of all modules packaged with this module
81 | ModuleList = @()
82 |
83 | # List of all files packaged with this module
84 | FileList = @()
85 |
86 | # Private data to pass to the module specified in ModuleToProcess
87 | PrivateData = ''
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 | [Dd]ebug/
46 | [Rr]elease/
47 | *_i.c
48 | *_p.c
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.vspscc
63 | .builds
64 | *.dotCover
65 |
66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
67 | packages/
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 |
76 | # Visual Studio profiler
77 | *.psess
78 | *.vsp
79 |
80 | # ReSharper is a .NET coding add-in
81 | _ReSharper*
82 |
83 | # NCrunch
84 | *.ncrunch*
85 |
86 | # Installshield output folder
87 | [Ee]xpress
88 |
89 | # DocProject is a documentation generator add-in
90 | DocProject/buildhelp/
91 | DocProject/Help/*.HxT
92 | DocProject/Help/*.HxC
93 | DocProject/Help/*.hhc
94 | DocProject/Help/*.hhk
95 | DocProject/Help/*.hhp
96 | DocProject/Help/Html2
97 | DocProject/Help/html
98 |
99 | # Click-Once directory
100 | publish
101 |
102 | # Others
103 | [Bb]in
104 | [Oo]bj
105 | sql
106 | TestResults
107 | *.Cache
108 | ClientBin
109 | stylecop.*
110 | ~$*
111 | *.dbmdl
112 | Generated_Code #added for RIA/Silverlight projects
113 |
114 | # Backup & report files from converting an old project file to a newer
115 | # Visual Studio version. Backup files are not needed, because we have git ;-)
116 | _UpgradeReport_Files/
117 | Backup*/
118 | UpgradeLog*.XML
119 |
120 |
121 |
122 | ############
123 | ## Windows
124 | ############
125 |
126 | # Windows image file caches
127 | Thumbs.db
128 |
129 | # Folder config file
130 | Desktop.ini
131 |
132 |
133 | #############
134 | ## Python
135 | #############
136 |
137 | *.py[co]
138 |
139 | # Packages
140 | *.egg
141 | *.egg-info
142 | dist
143 | build
144 | eggs
145 | parts
146 | bin
147 | var
148 | sdist
149 | develop-eggs
150 | .installed.cfg
151 |
152 | # Installer logs
153 | pip-log.txt
154 |
155 | # Unit test / coverage reports
156 | .coverage
157 | .tox
158 |
159 | #Translations
160 | *.mo
161 |
162 | #Mr Developer
163 | .mr.developer.cfg
164 |
165 | # Mac crap
166 | .DS_Store
167 |
--------------------------------------------------------------------------------
/Jump.Location.Specs/Jump.Location.Specs.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {C0A231D4-4E62-4E22-BD81-EB8257B15D21}
8 | Library
9 | Properties
10 | Jump.Location.Specs
11 | Jump.Location.Specs
12 | v4.0
13 | 512
14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | ..\
16 | true
17 |
18 |
19 |
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 | ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll
39 |
40 |
41 | ..\packages\Should.1.1.12.0\lib\Should.dll
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ..\packages\xunit.1.9.1\lib\net20\xunit.dll
52 |
53 |
54 | ..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | {813430AC-0181-4ACB-B731-C5F6578CEC64}
71 | Jump.Location
72 |
73 |
74 |
75 |
76 |
83 |
--------------------------------------------------------------------------------
/Jump.Location/SetJumpLocationCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Globalization;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Management.Automation;
8 |
9 | namespace Jump.Location
10 | {
11 | [Cmdlet(VerbsCommon.Set, "JumpLocation", DefaultParameterSetName = "Query")]
12 | public class SetJumpLocationCommand : PSCmdlet
13 | {
14 | private static readonly CommandController Controller = CommandController.DefaultInstance;
15 |
16 | public static IEnumerable GetTabExpansion(string line, string lastWord)
17 | {
18 | // line is something like "j term1 term2 temr3".
19 | // Skip cmdlet name and call match for the rest.
20 | string[] searchTerms = line.Split().Skip(1).ToArray();
21 | return Controller.GetMatchesForSearchTerm(searchTerms).Select(GetResultPath);
22 | }
23 |
24 | private static string GetResultPath(IRecord record)
25 | {
26 | var candidate = record.Path;
27 | if (candidate.Contains(" "))
28 | return string.Format("\"{0}\"", candidate);
29 | return candidate;
30 | }
31 |
32 | [Parameter(ParameterSetName = "Query", ValueFromRemainingArguments = true)]
33 | public string[] Query { get; set; }
34 |
35 | [Parameter(ParameterSetName = "Initialize",
36 | HelpMessage = "Initialize Jump-Location by starting to listen to directory changes.")]
37 | public SwitchParameter Initialize { get; set; }
38 |
39 | public static void UpdateTime(string location)
40 | {
41 | Controller.UpdateLocation(location);
42 | }
43 |
44 | protected override void ProcessRecord()
45 | {
46 | // This lets us do just `Jump-Location -Initialize` to initialize everything in the profile script
47 | if (Initialize)
48 | {
49 | InvokeCommand.InvokeScript(@"
50 | Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -Action {
51 | [Jump.Location.SetJumpLocationCommand]::UpdateTime($($(Get-Item -Path $(Get-Location))).PSPath)
52 | }
53 | ");
54 | return;
55 | }
56 |
57 | if (Query == null) { Query = new string[] {}; }
58 |
59 | // "j -" is an alias for popd.
60 | if (Query.Length == 1 && Query[0] == "-")
61 | {
62 | InvokeCommand.InvokeScript("Pop-Location");
63 | return;
64 | }
65 |
66 | // If last term is absolute path it's probably because of autocomplition
67 | // we can safely process it here.
68 | if (Query.Any() && Path.IsPathRooted(Query.Last()))
69 | {
70 | if (!Directory.Exists(Query.Last()))
71 | {
72 | throw new LocationNotFoundException(Query.Last());
73 | }
74 | ChangeDirectory(Query.Last());
75 | return;
76 | }
77 |
78 | IEnumerable orderedMatches = Controller.GetMatchesForSearchTerm(Query);
79 | if (orderedMatches == null) throw new LocationNotFoundException(String.Join(" ", Query));
80 | bool destinationFound = false;
81 | foreach (IRecord match in orderedMatches)
82 | {
83 | if (match.Provider == @"Microsoft.PowerShell.Core\FileSystem" && !Directory.Exists(match.Path))
84 | {
85 | WriteWarning(String.Format("Skipping {0}: directory not found. You can remove obsolete directories from database with command 'jumpstat -cleanup'.", match.Path));
86 | continue;
87 | }
88 | ChangeDirectory(match.Path);
89 | destinationFound = true;
90 | break;
91 | }
92 |
93 | if (!destinationFound)
94 | {
95 | // ask PowerShell to try to resolve the path (handling for things like "~")
96 | var resolvedPath = GetUnresolvedProviderPathFromPSPath(Query.Last());
97 |
98 | if (!Directory.Exists(resolvedPath))
99 | {
100 | throw new LocationNotFoundException(String.Join(" ", Query));
101 | }
102 | ChangeDirectory(Query.Last());
103 | }
104 | }
105 |
106 | private void ChangeDirectory(string fullPath)
107 | {
108 | InvokeCommand.InvokeScript(string.Format("Push-Location '{0}'", fullPath.Trim()));
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Jump.Location.Specs/FileStoreProviderSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading;
6 | using Should;
7 | using Xunit;
8 | using Xunit.Extensions;
9 |
10 | namespace Jump.Location.Specs
11 | {
12 | public class FileStoreProviderSpec
13 | {
14 | public class DescribeRevive : IDisposable
15 | {
16 | private readonly string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
17 |
18 | public void Dispose()
19 | {
20 | try
21 | {
22 | File.Delete(path);
23 | }
24 | catch
25 | {
26 | }
27 | }
28 |
29 | [Fact]
30 | public void It_can_Revive_a_single_record_of_correctly_formatted_text()
31 | {
32 | var lines = new[] {"8.5\tFS::C:\\blah"};
33 | File.WriteAllLines(path, lines);
34 | var provider = new FileStoreProvider(path);
35 |
36 | var db = provider.Revive();
37 | db.Records.Count().ShouldEqual(1);
38 | db.Records.First().Weight.ShouldEqual(8.5M);
39 | }
40 |
41 | [Fact]
42 | public void It_can_Revive_multiple_records_of_correctly_formatted_text()
43 | {
44 | var lines = new[]
45 | {
46 | "8.5\tFS::C:\\blah",
47 | "8.5\tFS::C:\\blah",
48 | "8.5\tFS::C:\\blah",
49 | };
50 | File.WriteAllLines(path, lines);
51 | var provider = new FileStoreProvider(path);
52 |
53 | var db = provider.Revive();
54 | db.Records.Count().ShouldEqual(3);
55 | }
56 |
57 | [Fact]
58 | public void It_can_Revive_a_record_with_invalid_weight()
59 | {
60 | var lines = new[] {"INVALID_WEIGHT\tFS::C:\\blah"};
61 | File.WriteAllLines(path, lines);
62 | var provider = new FileStoreProvider(path);
63 |
64 | var db = provider.Revive();
65 | db.Records.Count().ShouldEqual(1);
66 | db.Records.First().Weight.ShouldEqual(0M);
67 | db.Records.First().Path.ShouldEqual("C:\\blah");
68 | }
69 |
70 | [Fact]
71 | public void It_skips_blank_and_empty_lines()
72 | {
73 | var lines = new[]
74 | {
75 | "",
76 | "8.5\tFS::C:\\blah",
77 | " ",
78 | };
79 | File.WriteAllLines(path, lines);
80 | var provider = new FileStoreProvider(path);
81 |
82 | var db = provider.Revive();
83 | db.Records.Count().ShouldEqual(1);
84 | }
85 |
86 | [Fact]
87 | public void It_throws_InvalidOperationException_when_row_doesnt_have_2_columns()
88 | {
89 | var lines = new[] {"8.5 FS::C:\\blah"};
90 | File.WriteAllLines(path, lines);
91 | var provider = new FileStoreProvider(path);
92 |
93 | Assert.Throws(() => provider.Revive());
94 | }
95 | }
96 |
97 | public class DescribeSave : IDisposable
98 | {
99 | private readonly string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
100 |
101 | public void Dispose()
102 | {
103 | try
104 | {
105 | File.Delete(path);
106 | }
107 | catch
108 | {
109 | }
110 | }
111 |
112 | [Fact]
113 | public void It_can_save_a_single_record()
114 | {
115 | var db = new Database();
116 | db.Add(new Record("FS::C:\\data", 42M));
117 | var provider = new FileStoreProvider(path);
118 | provider.Save(db);
119 |
120 | var contents = File.ReadAllText(path);
121 | contents.ShouldEqual("42\tFS::C:\\data\r\n");
122 | }
123 |
124 | [Fact]
125 | public void It_can_save_a_single_record_with_weight_as_decimal_value()
126 | {
127 | var db = new Database();
128 | db.Add(new Record("FS::C:\\data", 42.5M));
129 | var provider = new FileStoreProvider(path);
130 | provider.Save(db);
131 |
132 | var contents = File.ReadAllText(path);
133 | contents.ShouldEqual("42.5\tFS::C:\\data\r\n");
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Jump.Location/GetJumpStatusCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Management.Automation;
4 |
5 | namespace Jump.Location
6 | {
7 | using System;
8 | using System.Configuration;
9 | using System.IO;
10 |
11 | [Cmdlet(VerbsCommon.Get, "JumpStatus", DefaultParameterSetName = "Query")]
12 | public class GetJumpStatusCommand : PSCmdlet
13 | {
14 | private static readonly CommandController Controller = CommandController.DefaultInstance;
15 |
16 | [Parameter(ParameterSetName = "Query", ValueFromRemainingArguments = true)]
17 | public string[] Query { get; set; }
18 |
19 | [Parameter(ParameterSetName = "Query", HelpMessage = "Only retrieve the first record matching the query. Same as `getj`")]
20 | public SwitchParameter First { get; set; }
21 |
22 | [Parameter(ParameterSetName = "Save", HelpMessage = "Saves any pending changes from setting weights on records explicitly.")]
23 | public SwitchParameter Save { get; set; }
24 |
25 | [Parameter(ParameterSetName = "Query", HelpMessage = "Includes all results, even if the weight is negative.")]
26 | public SwitchParameter All { get; set; }
27 |
28 | [Parameter(ParameterSetName = "Cleanup", HelpMessage = "Remove obsolete(not existing on the file system) records from DB.")]
29 | public SwitchParameter Cleanup { get; set; }
30 |
31 | [Parameter(ParameterSetName = "Scan", HelpMessage = "Scan and discover subdirectories.")]
32 | [AllowEmptyString]
33 | public string Scan { get; set; }
34 |
35 | protected override void ProcessRecord()
36 | {
37 |
38 | if (this.ParameterSetName == "Cleanup")
39 | {
40 | DoCleanup();
41 | return;
42 | }
43 |
44 | if (this.ParameterSetName == "Scan")
45 | {
46 | DoScan();
47 | return;
48 | }
49 |
50 | if (All || Query == null || Query.Length == 0)
51 | {
52 | ProcessSearch(Controller.GetOrderedRecords(All));
53 | }
54 | else
55 | {
56 | ProcessSearch(Controller.GetMatchesForSearchTerm(Query));
57 | }
58 | }
59 |
60 | private const string FileSystemProvider = @"Microsoft.PowerShell.Core\FileSystem";
61 | private void DoCleanup()
62 | {
63 | int recordsRemoved = 0;
64 | foreach (IRecord record in Controller.GetOrderedRecords(true))
65 | {
66 | if (record.Provider == FileSystemProvider && !Directory.Exists(record.Path))
67 | {
68 | Controller.RemoveRecord(record);
69 | recordsRemoved++;
70 | }
71 | }
72 | if (recordsRemoved > 0)
73 | {
74 | Controller.Save();
75 | WriteVerbose("Number of records cleaned: " + recordsRemoved + ".");
76 | }
77 | }
78 |
79 | private void DoScan()
80 | {
81 | if (string.IsNullOrEmpty(this.Scan))
82 | {
83 | // set default to current directory
84 | this.Scan = ".";
85 | }
86 | this.Scan = GetUnresolvedProviderPathFromPSPath(this.Scan);
87 | WriteVerbose("Discovering new folders.");
88 |
89 | int count = 0;
90 | foreach (string fullPath in GetChildFoldersRec(this.Scan))
91 | {
92 | WriteVerbose(string.Format("[Touching] {0}", fullPath));
93 | Controller.TouchRecord(string.Format("{0}::{1}", FileSystemProvider, fullPath));
94 | count++;
95 | }
96 |
97 | Controller.Save();
98 | WriteVerbose(string.Format("Number of directories touched: {0}.", count));
99 | }
100 |
101 | private IEnumerable GetChildFoldersRec(string path)
102 | {
103 | yield return path;
104 | foreach (string dir in Directory.GetDirectories(path))
105 | {
106 | foreach (string childDir in GetChildFoldersRec(dir))
107 | {
108 | yield return childDir;
109 | }
110 | }
111 | }
112 |
113 | private void ProcessSearch(IEnumerable records)
114 | {
115 | if (Save)
116 | {
117 | Controller.Save();
118 | return;
119 | }
120 |
121 | if (First)
122 | {
123 | var record = records.FirstOrDefault();
124 | if (record != null)
125 | WriteObject(record);
126 | return;
127 | }
128 |
129 | foreach (var record in records)
130 | {
131 | WriteObject(record);
132 | }
133 | WriteVerbose("Number of records: " + records.Count() + ".");
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Jump.Location/Jump.Location.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {813430AC-0181-4ACB-B731-C5F6578CEC64}
8 | Library
9 | Properties
10 | Jump.Location
11 | Jump.Location
12 | v3.5
13 | 512
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | false
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 | false
36 |
37 |
38 |
39 |
40 |
41 | OnBuildSuccess
42 |
43 |
44 |
45 | False
46 | ..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\Microsoft.PowerShell.Commands.Management.dll
47 |
48 |
49 |
50 |
51 |
52 |
53 | False
54 | ..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation.dll
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Component
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | PreserveNewest
78 |
79 |
80 | PreserveNewest
81 |
82 |
83 | PreserveNewest
84 |
85 |
86 | PreserveNewest
87 |
88 |
89 | PreserveNewest
90 |
91 |
92 | PreserveNewest
93 |
94 |
95 |
96 |
97 | PreserveNewest
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
116 |
--------------------------------------------------------------------------------
/Jump.Location/FileStoreProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Runtime.InteropServices;
7 | using System.Security.AccessControl;
8 | using System.Security.Principal;
9 | using System.Threading;
10 |
11 | namespace Jump.Location
12 | {
13 | public interface IFileStoreProvider
14 | {
15 | void Save(IDatabase database);
16 | IDatabase Revive();
17 | DateTime LastChangedDate { get; }
18 | }
19 |
20 | public class FileStoreUpdatedEventArgs : EventArgs
21 | {
22 | public FileStoreUpdatedEventArgs(IDatabase database)
23 | {
24 | UpdatedDatabase = database;
25 | }
26 |
27 | public IDatabase UpdatedDatabase { get; private set; }
28 | }
29 |
30 | public delegate void FileStoreUpdated(object sender, FileStoreUpdatedEventArgs args);
31 |
32 | class FileStoreProvider : IFileStoreProvider
33 | {
34 | private readonly string _path;
35 | private readonly string _pathTemp;
36 | private readonly string _mutexId;
37 | private readonly MutexSecurity _securitySettings;
38 |
39 | private const string TempPrefix = ".tmp";
40 |
41 | public FileStoreProvider(string path)
42 | {
43 | this._path = path;
44 | this._pathTemp = path + TempPrefix;
45 |
46 | // global mutex implementation from http://stackoverflow.com/questions/229565/what-is-a-good-pattern-for-using-a-global-mutex-in-c
47 |
48 | // get application GUID as defined in AssemblyInfo.cs
49 | string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString(CultureInfo.InvariantCulture);
50 |
51 | // unique id for global mutex - Global prefix means it is global to the machine
52 | _mutexId = string.Format("Global\\{{{0}}}", appGuid);
53 |
54 | // setting up security for multi-user usage
55 | // work also on localized systems (don't use just "Everyone")
56 | var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
57 | _securitySettings = new MutexSecurity();
58 | _securitySettings.AddAccessRule(allowEveryoneRule);
59 | }
60 |
61 | public event FileStoreUpdated FileStoreUpdated;
62 |
63 | private void OnFileStoreUpdated(object sender, FileStoreUpdatedEventArgs args)
64 | {
65 | if (FileStoreUpdated != null)
66 | FileStoreUpdated(sender, args);
67 | }
68 |
69 | public void Save(IDatabase database)
70 | {
71 | var lines = database.Records.Select(record =>
72 | string.Format("{1}\t{0}", record.FullName, record.Weight.ToString(CultureInfo.InvariantCulture)));
73 |
74 | bool createdNew;
75 | using (var mutex = new Mutex(false, _mutexId, out createdNew, _securitySettings))
76 | {
77 | var hasHandle = false;
78 | try
79 | {
80 | try
81 | {
82 | hasHandle = mutex.WaitOne(1000, false);
83 | if (hasHandle == false)
84 | {
85 | // ignore
86 | return;
87 | }
88 | }
89 | catch (AbandonedMutexException)
90 | {
91 | // Log the fact the mutex was abandoned in another process, it will still get aquired
92 | hasHandle = true;
93 | }
94 |
95 | // We can lose all history, if powershell will be closed during operation.
96 | File.WriteAllLines(_pathTemp, lines.ToArray());
97 | // NTFS guarantees atomic move operation http://stackoverflow.com/questions/774098/atomicity-of-file-move
98 | // So File.Move gurantees atomic, but doesn't support overwrite
99 | File.Copy(_pathTemp, _path, true);
100 | }
101 | finally
102 | {
103 | // edited by acidzombie24, added if statemnet
104 | if(hasHandle)
105 | mutex.ReleaseMutex();
106 | }
107 | }
108 | }
109 |
110 | public IDatabase Revive()
111 | {
112 | var db = new Database();
113 | var allLines = File.ReadAllLines(_path);
114 | var nonBlankLines = allLines.Where(line => !string.IsNullOrEmpty(line) && line.Trim() != string.Empty);
115 | foreach (var columns in nonBlankLines.Select(line => line.Split('\t')))
116 | {
117 | if (columns == null || columns.Length != 2)
118 | throw new InvalidOperationException("Row of file didn't have 2 columns separated by a tab");
119 |
120 | var weight = 0M;
121 | decimal.TryParse(columns[0], NumberStyles.Any, CultureInfo.InvariantCulture, out weight);
122 | var record = new Record(columns[1], weight);
123 | db.Add(record);
124 | }
125 | return db;
126 | }
127 |
128 | public DateTime LastChangedDate { get { return File.GetLastWriteTime(_path); } }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | This project is not actively maintained. Please consider using its successor [`ZLocation`](https://github.com/vors/ZLocation).
2 | =====================
3 |
4 |
5 | ---------------------
6 |
7 |
8 | Jump-Location: A cd that learns
9 | =====================
10 |
11 | If you spend any time in a console you know that `cd` is by far the most
12 | common command that you issue. I'll open up a console to it's default location
13 | in `C:\Users\tkellogg` or `C:\Windows\system32` and then issue a `cd C:\work\MyProject`.
14 | `Set-JumpLocation` is a cmdlet lets you issue a `j my` to jump
15 | directly to `C:\work\MyProject`.
16 |
17 | It learns your behavior by keeping track of how long you spend in certain
18 | directories and favoring them over the ones you don't care about. You don't
19 | have to use `Jump-Location` as a replacement for `cd`. Use `cd` to go local, and
20 | use `Set-JumpLocation` to jump further away.
21 |
22 | `Jump-Location` is a powershell implementation of [autojump][1].
23 |
24 |
25 | How it knows where you want to go
26 | ---------------------------------
27 |
28 | It keeps track of [how long you stay in a directory][2] and builds a database.
29 | When you use the `Set-JumpLocation` or `j` command, it looks through the database
30 | to find the most likely directory and jumps there. You should only need to
31 | give it a 2-3 character hint. For instance, on mine I can do:
32 |
33 | * `j de` -> `C:\Users\tkellogg\code\Jump-Location\bin\Debug`
34 | * `j doc` -> `C:\Users\tkellogg\Documents`
35 |
36 | What if you have several projects and you want to get to the Debug directory
37 | of one that you don't use very often? If you're `jumpstat` looks like this:
38 |
39 | 255 C:\Users\tkellogg\code\Jump-Location\bin\Debug
40 | 50 C:\Users\tkellogg\code\MongoDB.FSharp\bin\Debug
41 |
42 | Using `j de` will jump to `Jump-Location\bin\Debug`. But use something like
43 | `j mo d` if you really want to go to `MongoDB.FSharp\bin\Debug`. You can
44 | issue a `j mo d`. `mo` matches `MongoDB.FSharp` and `d` matches `Debug`.
45 |
46 | `j` internally calls `Push-Location`, so you can navigate back in your visited locations stack with `popd` or special Jump-Location query `j -`.
47 |
48 | Quick Primer on `jumpstat`
49 | --------------------------
50 |
51 | You can use `jumpstat` to see what's in the database. In tradition with `autojump`,
52 | the database is saved to `~\jump-location.txt`. You can open up that file and
53 | make changes. The file will auto-load into any open powershell sessions.
54 |
55 | Since we're in Powershell (and not legacy Bash) `jumpstat` returns _objects_.
56 | This means that you don't ever have to know anything about `~\jump-location.txt`.
57 | You can manipulate the objects it returns. For instance, this is valid:
58 |
59 | ```
60 | PS> $stats = jumpstat
61 | PS> $stats[10].Weight = -1
62 | PS> jumpstat -Save
63 | ```
64 |
65 | Setting a weight to a negative number like that will cause it to be blacklisted
66 | from all jump results. Use the `jumpstat -All` option to see negative weights
67 | included in the jumpstat result set.
68 |
69 | When you remove/rename directories, Jump-Location database can become out of sync with the file system. Your top-choice will pointed to an invalid location. You can **cleanup** database with `jumpstat -cleanup`. It will remove all records with non-existing paths.
70 |
71 | Jumpstat can also be used with the "scan" parameter (jumpstat -scan) to recursively
72 | scan sub-directories into the database with 0 Weight.
73 | This is a quick way to teach Jump-Location about related directories
74 | without having to manually cd'ing to each one individually.
75 |
76 | Add all subfolders of the current directory:
77 | ```
78 | jumpstat -scan .
79 | ```
80 |
81 | Installation
82 | ------------
83 |
84 | *Important:* Jump-Location requires PowerShell version 3 or higher. Older installations of Windows 7 may only have PowerShell version 2. [How to update powershell][7].
85 |
86 | **Recommended**: Install from [psget.net][8]:
87 | ```
88 | Install-Module Jump.Location
89 | ```
90 |
91 | There is also a [Chocolatey package][6] for Jump-Location. To install via Chocolaty
92 |
93 | ```
94 | choco install Jump-Location
95 | ```
96 |
97 | Otherwise you can still install it manually.
98 |
99 | 1. Download [the latest release][5].
100 | 2. Open properties for zip file and click "Unblock" button if you have one.
101 | 3. Unzip
102 | 4. Open a PowerShell console
103 | 5. Run `.\Install.ps1`. You may need to allow remote scripts by running
104 | `Set-ExecutionPolicy -RemoteSigned`. You may also have to right-click `Install.ps1`
105 | and Unblock it from the properties window.
106 | **Alternative:**
107 | Add line `Import-Module $modules\Jump-Location\Jump.Location.psd1` to your `$PROFILE`,
108 | where `$modules\Jump-Location` is a path to folder with module.
109 |
110 | Next time you open a PowerShell console Jump-Location will start learning
111 | your habits. You'll also have access to the `j` and `jumpstat` aliases.
112 |
113 | If you get errors after installation, try unblocking the file Jump.Location.dll manually by
114 | one of the following methods to clear the "untrusted" flag:
115 |
116 | 1. Copy the file to a FAT32 file system (such as a memory card) and back.
117 | 2. Run cmd /c "echo.>Jump.Location.dll:Zone.Identifier"
118 |
119 | If you find any bugs, please report them so I can fix them quickly!
120 |
121 | Build from source
122 | -----------------
123 | From root directory:
124 |
125 | 1. Run `msbuild` .
126 | 2. Run `.\copyBuild.ps1`
127 |
128 | In directory `Build` you will have local build of module.
129 |
130 | TODO
131 | ----------
132 | 1. Local search. `j . blah` will only match dirs under cwd. Using `.` will also search outside the DB.
133 | 2. Better PS documentation
134 |
135 | References
136 | ----------
137 | 1. [old releases][4].
138 |
139 | [1]: https://github.com/joelthelion/autojump
140 | [2]: http://stackoverflow.com/a/11813545/503826
141 | [3]: http://blogs.msdn.com/b/powershell/archive/2009/07/15/final-approved-verb-list-for-windows-powershell-2-0.aspx
142 | [4]: https://github.com/tkellogg/Jump-Location/downloads
143 | [5]: https://sourceforge.net/projects/jumplocation/files/latest/download
144 | [6]: https://chocolatey.org/packages/Jump-Location
145 | [7]: http://social.technet.microsoft.com/wiki/contents/articles/21016.how-to-install-windows-powershell-4-0.aspx
146 | [8]: http://psget.net/
147 |
--------------------------------------------------------------------------------
/.nuget/NuGet.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildProjectDirectory)\..\
5 |
6 |
7 | false
8 |
9 |
10 | false
11 |
12 |
13 | false
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
27 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
28 | $([System.IO.Path]::Combine($(SolutionDir), "packages"))
29 |
30 |
31 |
32 |
33 | $(SolutionDir).nuget
34 | packages.config
35 | $(SolutionDir)packages
36 |
37 |
38 |
39 |
40 | $(NuGetToolsPath)\nuget.exe
41 | @(PackageSource)
42 |
43 | "$(NuGetExePath)"
44 | mono --runtime=v4.0.30319 $(NuGetExePath)
45 |
46 | $(TargetDir.Trim('\\'))
47 |
48 |
49 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" -o "$(PackagesDir)"
50 | $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols
51 |
52 |
53 |
54 | RestorePackages;
55 | $(BuildDependsOn);
56 |
57 |
58 |
59 |
60 | $(BuildDependsOn);
61 | BuildPackage;
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
82 |
84 |
85 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/Jump.Location/CommandController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Management.Automation.Runspaces;
8 | using System.Reflection;
9 | using System.Threading;
10 |
11 | namespace Jump.Location
12 | {
13 | class CommandController
14 | {
15 | private IDatabase database;
16 | private readonly IFileStoreProvider fileStore;
17 | private bool needsToSave;
18 | // pin timer to prevent GC
19 | private Timer saveTimer;
20 |
21 | // In powershell_ise different tabs are represent different runspaces in the same process.
22 | // The prepor implementation requires ConditionalWeakTable from .NET 4.5
23 | private Dictionary _waitPeriodDictionary;
24 |
25 | private DateTime lastSaveDate = DateTime.Now;
26 | private static CommandController defaultInstance;
27 |
28 | internal CommandController(IDatabase database, IFileStoreProvider fileStore)
29 | {
30 | _waitPeriodDictionary = new Dictionary();
31 |
32 | // This is so that we can read config settings from DLL config file
33 | string configFile = Assembly.GetExecutingAssembly().Location + ".config";
34 | AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", configFile);
35 | ConfigurationManager.RefreshSection("appSettings");
36 |
37 | this.database = database;
38 | this.fileStore = fileStore;
39 | // We don't want write data to disk very often.
40 | // It's fine to lose jump data for last 2 seconds.
41 | saveTimer = new Timer(SaveCallback, null, 0, 2 * 1000);
42 | }
43 |
44 | public static CommandController DefaultInstance
45 | {
46 | get
47 | {
48 | if (defaultInstance == null)
49 | {
50 | var home = Environment.GetEnvironmentVariable("USERPROFILE");
51 | home = home ?? Path.Combine(Environment.GetEnvironmentVariable("HOMEDRIVE"), Environment.GetEnvironmentVariable("HOMEPATH"));
52 | var dbLocation = Path.Combine(home, "jump-location.txt");
53 | defaultInstance = Create(dbLocation);
54 | }
55 | return defaultInstance;
56 | }
57 | }
58 |
59 | public static CommandController Create(string path)
60 | {
61 | var fileStore = new FileStoreProvider(path);
62 | var database = File.Exists(path) ? fileStore.Revive() : new Database();
63 | return new CommandController(database, fileStore);
64 | }
65 |
66 | public void UpdateLocation(string fullName)
67 | {
68 | if (_waitPeriodDictionary.ContainsKey(Runspace.DefaultRunspace))
69 | {
70 | _waitPeriodDictionary[Runspace.DefaultRunspace].CloseAndUpdate();
71 | }
72 |
73 | var record = database.GetByFullName(fullName);
74 | _waitPeriodDictionary[Runspace.DefaultRunspace] = new DirectoryWaitPeriod(record, DateTime.Now);
75 | Save();
76 | }
77 |
78 | public IRecord TouchRecord(string fullName)
79 | {
80 | return database.GetByFullName(fullName);
81 | }
82 |
83 | public void Save()
84 | {
85 | needsToSave = true;
86 | }
87 |
88 | private void SaveCallback(object sender)
89 | {
90 | if (needsToSave)
91 | {
92 | try
93 | {
94 | needsToSave = false;
95 | fileStore.Save(database);
96 | lastSaveDate = DateTime.Now;
97 | }
98 | catch (Exception e)
99 | {
100 | EventLog.WriteEntry("Application", string.Format("{0}\r\n{1}", e, e.StackTrace));
101 | }
102 | }
103 | }
104 |
105 | private void ReloadIfNecessary()
106 | {
107 | if (fileStore.LastChangedDate <= lastSaveDate) return;
108 | database = fileStore.Revive();
109 | lastSaveDate = DateTime.Now;
110 | }
111 |
112 | public IRecord FindBest(params string[] search)
113 | {
114 | return GetMatchesForSearchTerm(search).FirstOrDefault();
115 | }
116 |
117 | public IEnumerable GetMatchesForSearchTerm(params string[] searchTerms)
118 | {
119 | List normalizedSearchTerms = new List();
120 | foreach (var term in searchTerms)
121 | {
122 | normalizedSearchTerms.AddRange(term.Split('\\'));
123 | }
124 | return GetMatchesForNormalizedSearchTerm(normalizedSearchTerms);
125 | }
126 |
127 | private IEnumerable GetMatchesForNormalizedSearchTerm(List searchTerms)
128 | {
129 | ReloadIfNecessary();
130 | var matches = new List();
131 | // Hack to return everything on empty search query.
132 | if (!searchTerms.Any())
133 | {
134 | searchTerms.Add("");
135 | }
136 | for (var i = 0; i < searchTerms.Count(); i++)
137 | {
138 | var isLast = i == searchTerms.Count()-1;
139 | var newMatches = GetMatchesForSingleSearchTerm(searchTerms[i], isLast);
140 | matches = i == 0 ? newMatches.ToList() : matches.Intersect(newMatches).ToList();
141 | }
142 |
143 | return matches;
144 | }
145 |
146 | private IEnumerable GetMatchesForSingleSearchTerm(string search, bool isLast)
147 | {
148 | var used = new HashSet();
149 | search = search.ToLower();
150 | foreach (var record in GetOrderedRecords()
151 | .Where(x => x.PathSegments.Last().StartsWith(search)))
152 | {
153 | used.Add(record.Path);
154 | yield return record;
155 | }
156 |
157 | foreach (var record in GetOrderedRecords()
158 | .Where(record => !used.Contains(record.Path))
159 | .Where(x => x.PathSegments.Last().Contains(search)))
160 | {
161 | used.Add(record.Path);
162 | yield return record;
163 | }
164 |
165 | if (isLast) yield break;
166 |
167 | foreach (var record in GetOrderedRecords()
168 | .Where(record => !used.Contains(record.Path))
169 | .Where(x => x.PathSegments.Any(s => s.StartsWith(search))))
170 | {
171 | used.Add(record.Path);
172 | yield return record;
173 | }
174 |
175 | foreach (var record in GetOrderedRecords()
176 | .Where(record => !used.Contains(record.Path))
177 | .Where(x => x.PathSegments.Any(s => s.Contains(search))))
178 | {
179 | used.Add(record.Path);
180 | yield return record;
181 | }
182 | }
183 |
184 | public IEnumerable GetOrderedRecords(bool includeAll = false)
185 | {
186 | return from record in database.Records
187 | where record.Weight >= 0 || includeAll
188 | orderby record.Weight descending
189 | select record;
190 | }
191 |
192 | public bool RemoveRecord(IRecord record)
193 | {
194 | return database.Remove(record);
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/Jump.Location.Specs/CommandControllerSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using Moq;
5 | using Should;
6 | using Xunit;
7 |
8 | namespace Jump.Location.Specs
9 | {
10 | public class CommandControllerSpec
11 | {
12 | public class DescribeUpdateTimes
13 | {
14 | private readonly Mock dbMock;
15 | private readonly Mock fsMock;
16 | private readonly CommandController controller;
17 |
18 | public DescribeUpdateTimes()
19 | {
20 | dbMock = new Mock();
21 | fsMock = new Mock();
22 | controller = new CommandController(dbMock.Object, fsMock.Object);
23 | }
24 |
25 | [Fact]
26 | public void It_saves_eventually()
27 | {
28 | var recordMock = new Mock();
29 | recordMock.SetupAllProperties();
30 | dbMock.Setup(x => x.GetByFullName(It.IsAny())).Returns(recordMock.Object);
31 |
32 | controller.UpdateLocation("foo");
33 | controller.UpdateLocation("bar");
34 | Thread.Sleep(30);
35 | fsMock.Verify(x => x.Save(dbMock.Object));
36 | }
37 |
38 | [Fact]
39 | public void It_updates_weights()
40 | {
41 | var recordMock = new Mock();
42 | recordMock.SetupAllProperties();
43 | dbMock.Setup(x => x.GetByFullName("foo")).Returns(recordMock.Object);
44 |
45 | controller.UpdateLocation("foo");
46 | Thread.Sleep(30);
47 | controller.UpdateLocation("foo");
48 |
49 | recordMock.Verify(x => x.AddTimeSpent(It.IsAny()), Times.Once());
50 | }
51 | }
52 |
53 | public class DescribeFindBest
54 | {
55 | private readonly Mock dbMock;
56 | private readonly Mock fsMock;
57 | private readonly CommandController controller;
58 |
59 | public DescribeFindBest()
60 | {
61 | dbMock = new Mock();
62 | fsMock = new Mock();
63 | controller = new CommandController(dbMock.Object, fsMock.Object);
64 | }
65 |
66 | [Fact]
67 | public void Exact_match_on_last_segment_from_a_DB_of_one()
68 | {
69 | dbMock.Setup(x => x.Records).Returns(new[]
70 | {
71 | new Record(@"FS::C:\Users\tkellogg", 10M),
72 | });
73 |
74 | var record = controller.FindBest("tkellogg");
75 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
76 | }
77 |
78 | [Fact]
79 | public void Exact_match_on_last_segment_from_a_DB_of_many()
80 | {
81 | dbMock.Setup(x => x.Records).Returns(new[]
82 | {
83 | new Record(@"FS::C:\Users\Kerianne", 10M),
84 | new Record(@"FS::C:\Users\lthompson", 10M),
85 | new Record(@"FS::C:\Users\tkellogg", 10M),
86 | });
87 |
88 | var record = controller.FindBest("tkellogg");
89 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
90 | }
91 |
92 | [Fact]
93 | public void No_match_returns_null()
94 | {
95 | dbMock.Setup(x => x.Records).Returns(new[]
96 | {
97 | new Record(@"FS::C:\Users\tkellogg", 10M),
98 | });
99 |
100 | var record = controller.FindBest("notfound");
101 | record.ShouldBeNull();
102 | }
103 |
104 | [Fact]
105 | public void Exact_match_on_last_segment_is_case_insensitive()
106 | {
107 | dbMock.Setup(x => x.Records).Returns(new[]
108 | {
109 | new Record(@"FS::C:\Users\TKELLOGG", 10M),
110 | });
111 |
112 | var record = controller.FindBest("tkellogg");
113 | record.Path.ShouldEqual(@"C:\Users\TKELLOGG");
114 | }
115 |
116 | [Fact]
117 | public void Last_segment_only_starts_with()
118 | {
119 | dbMock.Setup(x => x.Records).Returns(new[]
120 | {
121 | new Record(@"FS::C:\Users\tkellogg", 10M),
122 | });
123 |
124 | var record = controller.FindBest("t");
125 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
126 | }
127 |
128 | [Fact]
129 | public void Matches_substring_of_last_segment()
130 | {
131 | dbMock.Setup(x => x.Records).Returns(new[]
132 | {
133 | new Record(@"FS::C:\Users\tkellogg", 10M),
134 | });
135 |
136 | var record = controller.FindBest("ell");
137 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
138 | }
139 |
140 | #region Multiple arguments
141 |
142 | [Fact]
143 | public void Doesnt_match_only_middle_segments()
144 | {
145 | dbMock.Setup(x => x.Records).Returns(new[]
146 | {
147 | new Record(@"FS::C:\Users\tkellogg", 10M),
148 | });
149 |
150 | var record = controller.FindBest("users");
151 | record.ShouldBeNull();
152 | }
153 |
154 | [Fact]
155 | public void Exact_match_of_middle_segment_and_beginning_of_last()
156 | {
157 | dbMock.Setup(x => x.Records).Returns(new[]
158 | {
159 | new Record(@"FS::C:\Users\tkellogg", 10M),
160 | });
161 |
162 | var record = controller.FindBest("users", "tk");
163 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
164 | }
165 |
166 | [Fact]
167 | public void Middle_segment_starts_with_search_term_and_beginning_of_last()
168 | {
169 | dbMock.Setup(x => x.Records).Returns(new[]
170 | {
171 | new Record(@"FS::C:\Users\tkellogg", 10M),
172 | });
173 |
174 | var record = controller.FindBest("user", "tk");
175 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
176 | }
177 |
178 | [Fact]
179 | public void Substring_of_middle_segment_and_beginning_of_last()
180 | {
181 | dbMock.Setup(x => x.Records).Returns(new[]
182 | {
183 | new Record(@"FS::C:\Users\tkellogg", 10M),
184 | });
185 |
186 | var record = controller.FindBest("ers", "tk");
187 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
188 | }
189 |
190 | [Fact]
191 | public void Right_match_for_two_segments()
192 | {
193 | dbMock.Setup(x => x.Records).Returns(new[]
194 | {
195 | new Record(@"FS::C:\Users\foo", 10M),
196 | new Record(@"FS::C:\Users\bar", 20M),
197 | });
198 |
199 | var record = controller.FindBest("ers", "foo");
200 | record.Path.ShouldEqual(@"C:\Users\foo");
201 | }
202 |
203 | [Fact]
204 | public void Allow_column_in_search_terms()
205 | {
206 | dbMock.Setup(x => x.Records).Returns(new[]
207 | {
208 | new Record(@"FS::C:\Users\foo", 20M),
209 | new Record(@"FS::C:\Users\tkellogg", 10M),
210 | });
211 |
212 | var record = controller.FindBest("c:", "tke");
213 | record.Path.ShouldEqual(@"C:\Users\tkellogg");
214 | }
215 |
216 | [Fact]
217 | public void Same_search_term_3_times_for_last_segment()
218 | {
219 | dbMock.Setup(x => x.Records).Returns(new[]
220 | {
221 | new Record(@"FS::C:\Users\foo", 10M),
222 | new Record(@"FS::C:\Users\bar", 20M),
223 | });
224 |
225 | var record = controller.FindBest("foo", "foo", "foo");
226 | record.Path.ShouldEqual(@"C:\Users\foo");
227 | }
228 |
229 | [Fact]
230 | public void Same_search_term_2_times_for_midle_segment()
231 | {
232 | dbMock.Setup(x => x.Records).Returns(new[]
233 | {
234 | new Record(@"FS::C:\Users\foo", 10M),
235 | new Record(@"FS::C:\Users\bar", 20M),
236 | });
237 |
238 | var record = controller.FindBest("Users", "Users", "oo");
239 | record.Path.ShouldEqual(@"C:\Users\foo");
240 | }
241 |
242 | [Fact]
243 | public void One_of_segments_is_full_path()
244 | {
245 | dbMock.Setup(x => x.Records).Returns(new[]
246 | {
247 | new Record(@"FS::C:\Users\foo", 10M),
248 | new Record(@"FS::C:\Users\bar", 20M),
249 | });
250 |
251 | var record = controller.FindBest("ers", @"C:\Users\foo");
252 | record.Path.ShouldEqual(@"C:\Users\foo");
253 | }
254 |
255 | #endregion
256 |
257 | [Fact]
258 | public void Matches_are_ordered_by_weights()
259 | {
260 | dbMock.Setup(x => x.Records).Returns(new[]
261 | {
262 | new Record(@"FS::C:\Users\tkellogg", 10M),
263 | new Record(@"FS::C:\Users\tkellogg2", 13M),
264 | new Record(@"FS::C:\Users\tkellogg3", 15M),
265 | });
266 |
267 | var record = controller.GetMatchesForSearchTerm("");
268 | record.Select(x => x.Path).ToArray()
269 | .ShouldEqual(new[]{@"C:\Users\tkellogg3", @"C:\Users\tkellogg2", @"C:\Users\tkellogg"});
270 | }
271 |
272 | [Fact]
273 | public void It_doesnt_include_negative_weights()
274 | {
275 | dbMock.Setup(x => x.Records).Returns(new[]
276 | {
277 | new Record(@"FS::C:\Users\foo", -10M),
278 | });
279 |
280 | var record = controller.GetMatchesForSearchTerm("foo");
281 | record.Any().ShouldBeFalse("Negative record should not have been matched");
282 | }
283 |
284 | [Fact]
285 | public void It_updates_the_database_if_filesystem_is_newer()
286 | {
287 | fsMock.Setup(x => x.LastChangedDate).Returns(DateTime.Now.AddHours(1));
288 | fsMock.Setup(x => x.Revive()).Returns(Mock.Of());
289 |
290 | controller.FindBest("");
291 |
292 | fsMock.VerifyAll();
293 | }
294 |
295 | [Fact]
296 | public void Empty_query_return_most_popular()
297 | {
298 | dbMock.Setup(x => x.Records).Returns(new[]
299 | {
300 | new Record(@"FS::C:\Users\tkellogg", 10M),
301 | new Record(@"FS::C:\Users\tkellogg2", 13M),
302 | new Record(@"FS::C:\Users\tkellogg3", 15M),
303 | });
304 |
305 | var record = controller.GetMatchesForSearchTerm(new string[] {});
306 | record.Select(x => x.Path).ToArray()
307 | .ShouldEqual(new[] { @"C:\Users\tkellogg3", @"C:\Users\tkellogg2", @"C:\Users\tkellogg" });
308 | }
309 |
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------