Skip to content

Commit

Permalink
Round out all features and ensure tests work too
Browse files Browse the repository at this point in the history
  • Loading branch information
clintval committed Nov 11, 2023
1 parent 1c1e550 commit feedc75
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 54 deletions.
48 changes: 34 additions & 14 deletions plugins/nf-dotenv/src/main/nextflow/dotenv/DotenvExtension.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,62 @@ package nextflow.dotenv
import groovy.transform.CompileStatic
import io.github.cdimascio.dotenv.Dotenv
import io.github.cdimascio.dotenv.DotenvBuilder
import io.github.cdimascio.dotenv.DotenvException
import nextflow.Session
import nextflow.plugin.extension.Function
import nextflow.plugin.extension.PluginExtensionPoint

import java.nio.file.Path

/** An extension to make dotenv file variables available to Nextflow scripts through a function call. */
@CompileStatic
class DotenvExtension extends PluginExtensionPoint {

/** The default filename for the dotenv file. */
static final String DEFAULT_FILENAME = ".env"
static final String DEFAULT_FILENAME = '.env'

/** The configuration of this Nextflow session. */
private Map config

/** The dotenv environment result for this Nextflow session. */
private Dotenv dotenv
/** The directory where the dotenv file is supposed to be located. */
private Path directory

/** The default value to return when none is found in the environmental configuration file. */
private String defaultValue
/** The filename of the dotenv file used in this session. */
private String filename

/** Initializes the plugin once it is loaded and the session is ready. */
@Override
protected void init(Session session) {
this.config = session.config.navigate('dotenv', [:]) as Map
this.defaultValue = config.get("default", "").toString()
this.dotenv = new DotenvBuilder()
// The filename of the dotenv file which is typically '.env' but could be '.envrc' or other
.filename(config.get("filename", DEFAULT_FILENAME).toString())
// The relative directory to the main Nextflow script.
.directory(config.get("relative", session.baseDir.toUriString()).toString())
.load()
this.directory = session.baseDir.resolve(this.config.get('relative', '.'))
this.filename = config.get('filename', DEFAULT_FILENAME).toString()
}

/** Return a value in the dotenv environment, or an empty string if the key is missing. */
/** The dotenv environment for this Nextflow session. Marked as lazy to only raise exceptions at call time. */
@Lazy private Dotenv dotenv = {
try {
new DotenvBuilder()
.filename(this.filename)
.directory(this.directory.toString())
.load()
} catch (DotenvException) {
throw new DotenvException(
"Could not find dotenv file at path ${this.directory}/${this.filename}\n\n" +
"Consider modifying the following properties in your Nextflow config:\n\n" +
"\tdotenv.filename = '${DEFAULT_FILENAME}'\n" +
"\tdotenv.relative = '.'\n\n"
)
}
}()

/** Return a value in the dotenv environment, or raise an exception if the key is missing. */
@Function
String dotenv(String key) {
this.dotenv.get(key, this.defaultValue).toString()
def value = this.dotenv.get(key)
if (value == null) {
throw new DotenvException("Could not find key ${key} in dotenv ${this.directory}/${this.filename}")
} else {
return value
}
}
}
49 changes: 21 additions & 28 deletions plugins/nf-dotenv/src/test/nextflow/dotenv/DotenvTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,20 @@ class DotenvTest extends Dsl2Spec{
result.val == Channel.STOP
}

def 'should import the plugin and raise an exception if a dotenv is not found' () {
def 'should import the plugin and not raise an exception if a dotenv is not found but unused' () {
when:
String SCRIPT = '''
include { dotenv } from 'plugin/nf-dotenv'
channel.of('hi-mom')
'''
and:
new MockScriptRunner([:]).setScript(SCRIPT).execute()
def result = new MockScriptRunner([:]).setScript(SCRIPT).execute()
then:
thrown DotenvException
result.val == 'hi-mom'
result.val == Channel.STOP
}

def 'should import the plugin and raise no exceptions the dotenv is found' () {
def 'should import the plugin and raise no exceptions when the dotenv is found' () {
when:
String SCRIPT = '''
include { dotenv } from 'plugin/nf-dotenv'
Expand All @@ -85,7 +86,7 @@ class DotenvTest extends Dsl2Spec{
result.val == Channel.STOP
}

def 'should import the plugin and return an empty value for a key that does not exist' () {
def 'should import the plugin and by default throw an exception for a key that does not exist' () {
when:
String SCRIPT = '''
include { dotenv } from 'plugin/nf-dotenv'
Expand All @@ -95,14 +96,12 @@ class DotenvTest extends Dsl2Spec{
FOO=bar
'''
and:
def result = new MockScriptRunner([:]).setScript(SCRIPT).setDotenv(DOTENV).execute()
new MockScriptRunner([:]).setScript(SCRIPT).setDotenv(DOTENV).execute()
then:
result.val == ''
result.val == Channel.STOP
thrown DotenvException
}


def 'should import the plugin and return a the correct value for a key that does exist' () {
def 'should import the plugin and return the correct value for a key that does exist' () {
when:
String SCRIPT = '''
include { dotenv } from 'plugin/nf-dotenv'
Expand All @@ -119,7 +118,7 @@ class DotenvTest extends Dsl2Spec{
}


def 'should import the plugin and allow for an override of the dotenv filename relative to the main script' () {
def 'should import the plugin and allow for an override of the dotenv filename' () {
when:
String SCRIPT = '''
include { dotenv } from 'plugin/nf-dotenv'
Expand All @@ -128,14 +127,12 @@ class DotenvTest extends Dsl2Spec{
String DOTENV = '''
FOO=bar
'''
and:
def result = new MockScriptRunner(['dotenv.filename': '.envrc'])
new MockScriptRunner(['dotenv': ['filename': '.envrc']])
.setScript(SCRIPT)
.setDotenv(DOTENV, '.envrc')
.setDotenv(DOTENV,'.env')
.execute()
then:
result.val == 'bar'
result.val == Channel.STOP
thrown DotenvException
}


Expand All @@ -149,7 +146,7 @@ class DotenvTest extends Dsl2Spec{
FOO=bar
'''
and:
def result = new MockScriptRunner(['dotenv.relative': 'test'])
def result = new MockScriptRunner(['dotenv': ['relative': 'test']])
.setScript(SCRIPT)
.setDotenv(DOTENV, DotenvExtension.DEFAULT_FILENAME, 'test')
.execute()
Expand All @@ -158,7 +155,7 @@ class DotenvTest extends Dsl2Spec{
result.val == Channel.STOP
}

def 'should import the plugin and allow for an override of the dotenv directory as a parent directory to the main script' () {
def 'should import the plugin and allow for an override of the dotenv directory as the same as the env file' () {
when:
String SCRIPT = '''
include { dotenv } from 'plugin/nf-dotenv'
Expand All @@ -168,16 +165,16 @@ class DotenvTest extends Dsl2Spec{
FOO=bar
'''
and:
def result = new MockScriptRunner(['dotenv.relative': '../other'])
def result = new MockScriptRunner(['dotenv': ['relative': '.']])
.setScript(SCRIPT)
.setDotenv(DOTENV, DotenvExtension.DEFAULT_FILENAME, '../other')
.setDotenv(DOTENV, DotenvExtension.DEFAULT_FILENAME, '.')
.execute()
then:
result.val == 'bar'
result.val == Channel.STOP
}

def 'should import the plugin and allow for an override of the dotenv directory as the same as the env file' () {
def 'should import the plugin and raise an exception if the override dotenv directory is incorrect' () {
when:
String SCRIPT = '''
include { dotenv } from 'plugin/nf-dotenv'
Expand All @@ -187,15 +184,11 @@ class DotenvTest extends Dsl2Spec{
FOO=bar
'''
and:
def result = new MockScriptRunner(['dotenv.relative': '.'])
new MockScriptRunner(['dotenv': ['relative': 'other/']])
.setScript(SCRIPT)
.setDotenv(DOTENV, DotenvExtension.DEFAULT_FILENAME, '.')
.setDotenv(DOTENV, DotenvExtension.DEFAULT_FILENAME)
.execute()
then:
result.val == 'bar'
result.val == Channel.STOP
thrown DotenvException
}

// TODO: Need a test that tests for default value return from config
// TODO: Need a test for environment variable override
}
23 changes: 11 additions & 12 deletions plugins/nf-dotenv/src/test/nextflow/dotenv/MockHelpers.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ class MockScriptRunner extends ScriptRunner {
// /** An in-memory filesystem for unit testing. */
// static private FileSystem fs = Jimfs.newFileSystem(Configuration.unix())
//
// static private Path tmpDir = {
// private Path tmpDir = {
// Path tmp = fs.getPath('/tmp')
// tmp.mkdir()
// Path test = Files.createTempDirectory(tmp, 'test')
// test
// }()
// }

/** Return a random temporary directory for testing. */
static private Path tmpDir = {
private Path tmpDir = {
Path tmp = Path.of(System.getProperty('java.io.tmpdir'))
tmp.mkdir()
Path test = Files.createTempDirectory(tmp, 'test')
Expand All @@ -58,13 +58,13 @@ class MockScriptRunner extends ScriptRunner {
* @param content The content of the temporary file, if any.
* @param relative The relative modifier to the temporary file e.g. 'child-folder/'
*/
static private Path createInMemTempFile(String name, String content=null, String relative=null) {
private Path createInMemTempFile(String name, String content=null, String relative=null) {
Path result
if (relative) {
tmpDir.resolve(relative).mkdir()
result = tmpDir.resolve(relative).resolve(name)
this.tmpDir.resolve(relative).mkdir()
result = this.tmpDir.resolve(relative).resolve(name)
} else {
result = tmpDir.resolve(name)
result = this.tmpDir.resolve(name)
}
if (content) {
result.text = content
Expand All @@ -79,17 +79,16 @@ class MockScriptRunner extends ScriptRunner {
}

/** Set the script `main.nf` with specific contents. */
MockScriptRunner setScript(String str) {
Path script = createInMemTempFile('main.nf', str)
MockScriptRunner setScript(String content) {
Path script = createInMemTempFile('main.nf', content)
setScript(script)
return this
}

/** Set the configuration file `.env` with specific contents. */
MockScriptRunner setDotenv(String str, String filename=DotenvExtension.DEFAULT_FILENAME, String relative=null) {
createInMemTempFile(filename, str, relative)
MockScriptRunner setDotenv(String content, String filename=DotenvExtension.DEFAULT_FILENAME, String relative=null) {
createInMemTempFile(filename, content, relative)
return this

}

/** Normalize the output of the script result so it is easier to compare. */
Expand Down

0 comments on commit feedc75

Please sign in to comment.