Skip to content

Commit cc1a447

Browse files
committed
Implement shed_init command.
Currently just creates .shed.yml files from command-line arguments. Arguably the tool author could just open this file and start editing but at least --help provides some sort guidance of what fields are available and validates categories for instance. The real reason to add this however is as a stepping stone toward #118.
1 parent 9061d16 commit cc1a447

File tree

11 files changed

+234
-14
lines changed

11 files changed

+234
-14
lines changed

planemo/commands/cmd_shed_init.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import click
2+
import sys
3+
4+
from planemo.cli import pass_context
5+
6+
from planemo import options
7+
from planemo import shed
8+
9+
10+
@click.command("shed_init")
11+
@options.optional_project_arg()
12+
# TODO:
13+
# @click.option(
14+
# "--from_workflow",
15+
# type=click.Path(exists=True, file_okay=True, resolve_path=True),
16+
# help=('Attempt to generate repository dependencies from specified '
17+
# 'workflow.')
18+
# )
19+
@click.option(
20+
"--description",
21+
help='Specify repository description for .shed.yml.'
22+
)
23+
@click.option(
24+
"--long_description",
25+
help='Specify repository long_description for .shed.yml.'
26+
)
27+
@click.option(
28+
"--remote_repository_url",
29+
help='Specify repository remote_repository_url for .shed.yml.'
30+
)
31+
@click.option(
32+
"--homepage_url",
33+
help='Specify repository homepage_url for .shed.yml.'
34+
)
35+
@click.option(
36+
"--category",
37+
multiple=True,
38+
help='Specify repository category for .shed.yml (may specify multiple).',
39+
type=click.Choice(shed.CURRENT_CATEGORIES)
40+
)
41+
@options.shed_owner_option()
42+
@options.shed_name_option()
43+
@options.force_option()
44+
@pass_context
45+
def cli(ctx, path, **kwds):
46+
exit_code = shed.shed_init(ctx, path, **kwds)
47+
sys.exit(exit_code)

planemo/commands/cmd_tool_init.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import os
2+
import sys
3+
24
import click
5+
36
from planemo.cli import pass_context
7+
from planemo import options
48
from planemo import io
59
from planemo import tool_builder
610

@@ -18,12 +22,7 @@
1822
prompt=True,
1923
help="Short identifier for new tool (no whitespace)",
2024
)
21-
@click.option(
22-
"-f",
23-
"--force",
24-
is_flag=True,
25-
help="Overwrite existing tool if present.",
26-
)
25+
@options.force_option(what="tool")
2726
@click.option(
2827
"-t",
2928
"--tool",
@@ -190,9 +189,8 @@ def cli(ctx, **kwds):
190189
output = kwds.get("tool")
191190
if not output:
192191
output = "%s.xml" % kwds.get("id")
193-
if not kwds["force"] and os.path.exists(output):
194-
io.error("%s already exists, exiting." % output)
195-
return 1
192+
if not io.can_write_to_path(output, **kwds):
193+
sys.exit(1)
196194
tool_description = tool_builder.build(**kwds)
197195
open(output, "w").write(tool_description.contents)
198196
io.info("Tool written to %s" % output)

planemo/io.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ def info(message, *args):
1818
_echo(click.style(message, bold=True, fg='green'))
1919

2020

21+
def can_write_to_path(path, **kwds):
22+
if not kwds["force"] and os.path.exists(path):
23+
error("%s already exists, exiting." % path)
24+
return False
25+
return True
26+
27+
2128
def error(message, *args):
2229
if args:
2330
message = message % args

planemo/options.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
from galaxy.tools.deps import docker_util
55

66

7+
def force_option(what="files"):
8+
return click.option(
9+
"-f",
10+
"--force",
11+
is_flag=True,
12+
help="Overwrite existing %s if present." % what,
13+
)
14+
15+
716
def test_data_option():
817
return click.option(
918
"--test_data",

planemo/shed.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
except ImportError:
1717
toolshed = None
1818

19-
from planemo.io import error
20-
from planemo.io import untar_to
19+
from planemo.io import (
20+
error,
21+
untar_to,
22+
can_write_to_path,
23+
)
2124

2225
SHED_CONFIG_NAME = '.shed.yml'
2326
NO_REPOSITORIES_MESSAGE = ("Could not find any .shed.yml files or a --name to "
@@ -57,6 +60,54 @@
5760
REPO_TYPE_TOOL_DEP = "tool_dependency_definition"
5861
REPO_TYPE_SUITE = "repository_suite_definition"
5962

63+
# Generate with python scripts/categories.py
64+
CURRENT_CATEGORIES = [
65+
"Assembly",
66+
"ChIP-seq",
67+
"Combinatorial Selections",
68+
"Computational chemistry",
69+
"Convert Formats",
70+
"Data Managers",
71+
"Data Source",
72+
"Fasta Manipulation",
73+
"Fastq Manipulation",
74+
"Genome-Wide Association Study",
75+
"Genomic Interval Operations",
76+
"Graphics",
77+
"Imaging",
78+
"Metabolomics",
79+
"Metagenomics",
80+
"Micro-array Analysis",
81+
"Next Gen Mappers",
82+
"Ontology Manipulation",
83+
"Phylogenetics",
84+
"Proteomics",
85+
"RNA",
86+
"SAM",
87+
"Sequence Analysis",
88+
"Statistics",
89+
"Systems Biology",
90+
"Text Manipulation",
91+
"Tool Dependency Packages",
92+
"Tool Generators",
93+
"Transcriptomics",
94+
"Variant Analysis",
95+
"Visualization",
96+
"Web Services",
97+
]
98+
99+
100+
def shed_init(ctx, path, **kwds):
101+
if not os.path.exists(path):
102+
os.makedirs(path)
103+
shed_config_path = os.path.join(path, SHED_CONFIG_NAME)
104+
if not can_write_to_path(shed_config_path, **kwds):
105+
# .shed.yml exists and no --force sent.
106+
return 1
107+
108+
_create_shed_config(ctx, shed_config_path, **kwds)
109+
return 0
110+
60111

61112
def shed_repo_config(path):
62113
shed_yaml_path = os.path.join(path, SHED_CONFIG_NAME)
@@ -290,6 +341,34 @@ def realize_effective_repositories(path, **kwds):
290341
shutil.rmtree(temp_directory)
291342

292343

344+
def _create_shed_config(ctx, path, **kwds):
345+
name = kwds.get("name", None) or path_to_repo_name(os.path.dirname(path))
346+
owner = kwds.get("owner", None)
347+
if owner is None:
348+
owner = ctx.global_config.get("shed_username", None)
349+
description = kwds.get("description", None) or name
350+
long_description = kwds.get("long_description", None)
351+
remote_repository_url = kwds.get("remote_repository_url", None)
352+
homepage_url = kwds.get("homepage_url", None)
353+
categories = kwds.get("category", [])
354+
config = dict(
355+
name=name,
356+
owner=owner,
357+
description=description,
358+
long_description=long_description,
359+
remote_repository_url=remote_repository_url,
360+
homepage_url=homepage_url,
361+
categories=categories,
362+
)
363+
# Remove empty entries...
364+
for k in list(config.keys()):
365+
if config[k] is None:
366+
del config[k]
367+
368+
with open(path, "w") as f:
369+
yaml.dump(config, f)
370+
371+
293372
def _find_raw_repositories(path, **kwds):
294373
name = kwds.get("name", None)
295374
recursive = kwds.get("recursive", False)

planemo/shed_lint.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
REPO_TYPE_UNRESTRICTED,
99
REPO_TYPE_TOOL_DEP,
1010
REPO_TYPE_SUITE,
11+
CURRENT_CATEGORIES,
1112
)
1213
from planemo.tool_lint import (
1314
build_lint_args,
@@ -111,6 +112,7 @@ def _lint_if_present(key, func, *args):
111112
_lint_if_present("owner", _validate_repo_owner)
112113
_lint_if_present("name", _validate_repo_name)
113114
_lint_if_present("type", _validate_repo_type, effective_name)
115+
_lint_if_present("categories", _validate_categories)
114116

115117

116118
def _validate_repo_type(repo_type, name):
@@ -152,3 +154,18 @@ def _validate_repo_owner(owner):
152154
if not(VALID_PUBLICNAME_RE.match(owner)):
153155
msg = "Owner must contain only lower-case letters, numbers and '-'"
154156
return msg
157+
158+
159+
def _validate_categories(categories):
160+
msg = None
161+
if len(categories) == 0:
162+
msg = "Repository should specify one or more categories."
163+
else:
164+
for category in categories:
165+
unknown_categories = []
166+
if category not in CURRENT_CATEGORIES:
167+
unknown_categories.append(category)
168+
if unknown_categories:
169+
msg = "Categories [%s] unknown." % unknown_categories
170+
171+
return msg

scripts/categories.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from __future__ import print_function
2+
import requests
3+
4+
5+
categories = requests.get("https://testtoolshed.g2.bx.psu.edu/api/categories").json()
6+
print("CURRENT_CATEGORIES = [")
7+
for c in map(lambda c: c["name"], categories):
8+
if c["
9+
print(' "%s",' % c)
10+
print("]")
11+

tests/data/repos/single_tool/.shed.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ type: "unrestricted"
55
remote_repository_url: "https://github.com/galaxyproject/planemo/tree/master/tests/data/repos/single_tool"
66
homepage_url: "http://planemo.readthedocs.org/en/latest/"
77
categories:
8-
- "Text Utils"
8+
- "Text Manipulation"

tests/shed_app_test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def mock_model(directory):
2121
return InMemoryShedDataModel(
2222
directory
2323
).add_category(
24-
"c1", "Text Utils"
24+
"c1", "Text Manipulation"
2525
).add_category(
2626
"c2", "Sequence Analysis"
2727
).add_repository(

tests/test_shed_init.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
3+
import yaml
4+
5+
from .test_utils import CliShedTestCase
6+
7+
8+
class ShedInitTestCase(CliShedTestCase):
9+
10+
def test_minimal(self):
11+
with self._isolate() as f:
12+
name = os.path.basename(os.path.abspath(f))
13+
self._check_exit_code(["shed_init", "--owner", "iuc"])
14+
shed_config_path = os.path.join(f, ".shed.yml")
15+
assert os.path.exists(shed_config_path)
16+
shed_config = yaml.load(open(shed_config_path, "r"))
17+
assert shed_config["name"] == name
18+
assert shed_config["owner"] == "iuc"
19+
assert shed_config["description"] == name
20+
assert len(shed_config["categories"]) == 0
21+
22+
def test_more_options(self):
23+
with self._isolate() as f:
24+
repo_url = "https://github.com/galaxyproject/tools-devteam"
25+
init_command = [
26+
"shed_init",
27+
"--owner", "devteam",
28+
"--name", "samtools-filter",
29+
"--description", "A samtools repo",
30+
"--long_description", "A longer description.",
31+
"--remote_repository_url",
32+
repo_url,
33+
"--homepage_url", "https://example.com/",
34+
"--category", "SAM",
35+
"--category", "Sequence Analysis",
36+
"--category", "Statistics",
37+
]
38+
self._check_exit_code(init_command)
39+
shed_config_path = os.path.join(f, ".shed.yml")
40+
assert os.path.exists(shed_config_path)
41+
shed_config = yaml.load(open(shed_config_path, "r"))
42+
assert shed_config["name"] == "samtools-filter"
43+
assert shed_config["owner"] == "devteam"
44+
assert shed_config["description"] == "A samtools repo"
45+
assert shed_config["long_description"] == "A longer description."
46+
assert shed_config["remote_repository_url"] == repo_url
47+
assert shed_config["homepage_url"] == "https://example.com/"
48+
49+
categories = shed_config["categories"]
50+
assert len(categories) == 3
51+
assert "SAM" in categories
52+
assert "Statistics" in categories

0 commit comments

Comments
 (0)