Merge pull request #5535 from atom/directory-provider

Introduce atom.directory-provider service.
This commit is contained in:
Max Brunsfeld
2015-02-19 21:54:54 -08:00
4 changed files with 161 additions and 14 deletions

View File

@@ -0,0 +1,40 @@
DefaultDirectoryProvider = require "../src/default-directory-provider"
path = require "path"
temp = require "temp"
describe "DefaultDirectoryProvider", ->
describe ".directoryForURISync(uri)", ->
it "returns a Directory with a path that matches the uri", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
directory = provider.directoryForURISync(tmp)
expect(directory.getPath()).toEqual tmp
it "normalizes its input before creating a Directory for it", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
nonNormalizedPath = tmp + path.sep + ".." + path.sep + path.basename(tmp)
expect(tmp.contains("..")).toBe false
expect(nonNormalizedPath.contains("..")).toBe true
directory = provider.directoryForURISync(nonNormalizedPath)
expect(directory.getPath()).toEqual tmp
it "creates a Directory for its parent dir when passed a file", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
file = path.join(tmp, "example.txt")
fs.writeFileSync(file, "data")
directory = provider.directoryForURISync(file)
expect(directory.getPath()).toEqual tmp
describe ".directoryForURI(uri)", ->
it "returns a Promise that resolves to a Directory with a path that matches the uri", ->
provider = new DefaultDirectoryProvider()
tmp = temp.mkdirSync()
waitsForPromise ->
provider.directoryForURI(tmp).then (directory) ->
expect(directory.getPath()).toEqual tmp

View File

@@ -14,6 +14,70 @@ describe "Project", ->
atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')])
describe "constructor", ->
it "enables a custom DirectoryProvider to supersede the DefaultDirectoryProvider", ->
remotePath = "ssh://foreign-directory:8080/"
class DummyDirectory
constructor: (@path) ->
getPath: -> @path
getFile: -> existsSync: -> false
getSubdirectory: -> existsSync: -> false
isRoot: -> true
off: ->
contains: (filePath) -> filePath.startsWith(remotePath)
directoryProvider =
directoryForURISync: (uri) ->
if uri.startsWith("ssh://")
new DummyDirectory(uri)
else
null
directoryForURI: (uri) -> throw new Error("This should not be called.")
atom.packages.serviceHub.provide(
"atom.directory-provider", "0.1.0", directoryProvider)
tmp = temp.mkdirSync()
atom.project.setPaths([tmp, remotePath])
directories = atom.project.getDirectories()
expect(directories.length).toBe 2
localDirectory = directories[0]
expect(localDirectory.getPath()).toBe tmp
expect(localDirectory instanceof Directory).toBe true
dummyDirectory = directories[1]
expect(dummyDirectory.getPath()).toBe remotePath
expect(dummyDirectory instanceof DummyDirectory).toBe true
expect(atom.project.getPaths()).toEqual([tmp, remotePath])
# Make sure that DummyDirectory.contains() is honored.
remotePathSubdirectory = remotePath + "a/subdirectory"
atom.project.addPath(remotePathSubdirectory)
expect(atom.project.getDirectories().length).toBe 2
# Make sure that a new DummyDirectory that is not contained by the first
# DummyDirectory can be added.
otherRemotePath = "ssh://other-foreign-directory:8080/"
atom.project.addPath(otherRemotePath)
newDirectories = atom.project.getDirectories()
expect(newDirectories.length).toBe 3
otherDummyDirectory = newDirectories[2]
expect(otherDummyDirectory.getPath()).toBe otherRemotePath
expect(otherDummyDirectory instanceof DummyDirectory).toBe true
it "uses the default directory provider if no custom provider can handle the URI", ->
directoryProvider =
directoryForURISync: (uri) -> null
directoryForURI: (uri) -> throw new Error("This should not be called.")
atom.packages.serviceHub.provide(
"atom.directory-provider", "0.1.0", directoryProvider)
tmp = temp.mkdirSync()
atom.project.setPaths([tmp])
directories = atom.project.getDirectories()
expect(directories.length).toBe 1
expect(directories[0].getPath()).toBe tmp
it "tries to update repositories when a new RepositoryProvider is registered", ->
tmp = temp.mkdirSync('atom-project')
atom.project.setPaths([tmp])

View File

@@ -0,0 +1,35 @@
{Directory} = require 'pathwatcher'
fs = require 'fs-plus'
path = require 'path'
module.exports =
class DefaultDirectoryProvider
# Public: Create a Directory that corresponds to the specified URI.
#
# * `uri` {String} The path to the directory to add. This is guaranteed not to
# be contained by a {Directory} in `atom.project`.
#
# Returns:
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURISync: (uri) ->
projectPath = path.normalize(uri)
directoryPath = if fs.isDirectorySync(projectPath)
projectPath
else
path.dirname(projectPath)
new Directory(directoryPath)
# Public: Create a Directory that corresponds to the specified URI.
#
# * `uri` {String} The path to the directory to add. This is guaranteed not to
# be contained by a {Directory} in `atom.project`.
#
# Returns a Promise that resolves to:
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURI: (uri) ->
Promise.resolve(@directoryForURISync(uri))

View File

@@ -8,9 +8,9 @@ Q = require 'q'
{Model} = require 'theorist'
{Subscriber} = require 'emissary'
{Emitter} = require 'event-kit'
DefaultDirectoryProvider = require './default-directory-provider'
Serializable = require 'serializable'
TextBuffer = require 'text-buffer'
{Directory} = require 'pathwatcher'
Grim = require 'grim'
TextEditor = require './text-editor'
@@ -41,6 +41,14 @@ class Project extends Model
@rootDirectories = []
@repositories = []
@directoryProviders = [new DefaultDirectoryProvider()]
atom.packages.serviceHub.consume(
'atom.directory-provider',
'^0.1.0',
# New providers are added to the front of @directoryProviders because
# DefaultDirectoryProvider is a catch-all that will always provide a Directory.
(provider) => @directoryProviders.unshift(provider))
# Mapping from the real path of a {Directory} to a {Promise} that resolves
# to either a {Repository} or null. Ideally, the {Directory} would be used
# as the key; however, there can be multiple {Directory} objects created for
@@ -159,7 +167,7 @@ class Project extends Model
# Public: Get an {Array} of {String}s containing the paths of the project's
# directories.
getPaths: -> rootDirectory.path for rootDirectory in @rootDirectories
getPaths: -> rootDirectory.getPath() for rootDirectory in @rootDirectories
getPath: ->
Grim.deprecate("Use ::getPaths instead")
@getPaths()[0]
@@ -182,22 +190,22 @@ class Project extends Model
Grim.deprecate("Use ::setPaths instead")
@setPaths([path])
# Public: Add a path the project's list of root paths
# Public: Add a path to the project's list of root paths
#
# * `projectPath` {String} The path to the directory to add.
addPath: (projectPath, options) ->
projectPath = path.normalize(projectPath)
for directory in @getDirectories()
# Apparently a Directory does not believe it can contain itself, so we
# must also check whether the paths match.
return if directory.contains(projectPath) or directory.getPath() is projectPath
directoryPath = if fs.isDirectorySync(projectPath)
projectPath
else
path.dirname(projectPath)
return if @getPaths().some (existingPath) ->
(directoryPath is existingPath) or
(directoryPath.indexOf(path.join(existingPath, path.sep)) is 0)
directory = new Directory(directoryPath)
directory = null
for provider in @directoryProviders
break if directory = provider.directoryForURISync?(projectPath)
if directory is null
# This should never happen because DefaultDirectoryProvider should always
# return a Directory.
throw new Error(projectPath + ' could not be resolved to a directory')
@rootDirectories.push(directory)
repo = null