[^<]+<\/a>\s*(\d{2}-\w{3}-\d{4} \d{2}:\d{2})\s*(\d+)$')
19 |
20 | GALAXY_SINGULARITY_IMAGES_URL = 'https://depot.galaxyproject.org/singularity/'
21 | BASE_QUAYIO_DOCKER_URL = 'quay.io/biocontainers/'
22 |
23 |
24 | def format_size_mb(size: str) -> str:
25 | size_mb = int(size) / (1024**2)
26 | return f'{size_mb:0.1f} MB'
27 |
28 |
29 | def to_isodatetime(s: str, fmt: str = '%d-%b-%Y %H:%M') -> str:
30 | return datetime.strptime(s, fmt).isoformat()
31 |
32 |
33 | def get_images() -> List[Tuple[str, str, str]]:
34 | """Get list of tuples with image name, date modified and size in MB"""
35 | images = []
36 | print(f'Fetching Biocontainers image info from {GALAXY_SINGULARITY_IMAGES_URL}')
37 | with urlopen(GALAXY_SINGULARITY_IMAGES_URL) as response:
38 | for line in response:
39 | line = line.decode('utf-8').strip()
40 | if not line:
41 | continue
42 | m = regex_sing_img.match(line)
43 | if m:
44 | image_name, dt, size = m.groups()
45 | image_name = unquote_plus(image_name)
46 | isodate = to_isodatetime(dt)
47 | size_mb = format_size_mb(size)
48 | images.append((image_name, isodate, size_mb))
49 | print(f'Fetched info for {len(images)} Biocontainer images from {GALAXY_SINGULARITY_IMAGES_URL}')
50 | return images
51 |
52 |
53 | def cache_images_list(images: List[Tuple[str, str, str]]) -> None:
54 | cache_dir = Path(sublime.cache_path()) / 'sublime-nextflow'
55 | cache_dir.mkdir(parents=True, exist_ok=True)
56 | cache_path = cache_dir / 'singularity_images.pickle'
57 | with open(cache_path, 'wb') as fh:
58 | pickle.dump(images, fh)
59 |
60 |
61 | def fetch_images(window: sublime.Window) -> None:
62 | images = get_images()
63 | cache_images_list(images)
64 | window.status_message(f'Retrieved and cached info for {len(images)} Biocontainer images from {GALAXY_SINGULARITY_IMAGES_URL}')
65 |
66 |
67 | def get_cached_images_list() -> Optional[List[Tuple[str, str, str]]]:
68 | cache_dir = Path(sublime.cache_path()) / 'sublime-nextflow'
69 | cache_path = cache_dir / 'singularity_images.pickle'
70 | if not cache_path.exists():
71 | return None
72 | with open(cache_path, 'rb') as fh:
73 | return pickle.load(fh)
74 |
75 |
76 | class NextflowBiocontainerInfoFetchCommand(sublime_plugin.WindowCommand):
77 | def run(self):
78 | with container_fetch_lock:
79 | self.window.status_message('Fetching Biocontainers Docker and Singularity images information...')
80 | thread = threading.Thread(target=fetch_images, args=(self.window, ))
81 | thread.daemon = True
82 | thread.start()
83 |
84 |
85 | class NextflowBiocontainerDirectiveInsertCommand(sublime_plugin.TextCommand):
86 | def run(self, edit, **kwargs):
87 | container = kwargs.get('container', None)
88 | if container:
89 | view = self.view
90 | if len(view.selection) > 1:
91 | return
92 | region = view.selection[0]
93 | point = region.a
94 | row, col = view.rowcol(point)
95 | pad_col = ' ' * col
96 | name = container[0]
97 | text = "if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) {\n"
98 | text += f"{pad_col} container '{GALAXY_SINGULARITY_IMAGES_URL}{name}'\n"
99 | text += f"{pad_col}" + "} else {\n"
100 | text += f"{pad_col} container '{BASE_QUAYIO_DOCKER_URL}{name}'\n"
101 | text += f"{pad_col}" + "}\n"
102 | view.insert(edit, point, text)
103 |
104 |
105 | class NextflowBiocontainerSelectCommand(sublime_plugin.TextCommand):
106 | def run(self, edit, **kwargs):
107 | view = self.view
108 | if len(view.selection) > 1:
109 | return
110 | region = view.selection[0]
111 | point = region.a
112 | window = view.window()
113 | singularity_images = get_cached_images_list()
114 | if singularity_images:
115 | window.status_message(f'Retrieved {len(singularity_images)} singularity images from cache')
116 | else:
117 | window.status_message(f'Getting singularity images from {GALAXY_SINGULARITY_IMAGES_URL}')
118 | singularity_images = get_images()
119 | window.status_message(f'Retrieved {len(singularity_images)} singularity images')
120 | cache_images_list(singularity_images)
121 | window.status_message(f'Cached info for {len(singularity_images)} singularity images')
122 |
123 | def on_select(x: int):
124 | if x == -1:
125 | return
126 | container = singularity_images[x]
127 | view.run_command('nextflow_biocontainer_directive_insert', dict(container=container))
128 |
129 | view.window().show_quick_panel([f'{name} ({dt}) [{size_mb}]' for name, dt, size_mb in singularity_images], on_select=on_select)
130 |
--------------------------------------------------------------------------------
/images/conda-completion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/conda-completion.png
--------------------------------------------------------------------------------
/images/container-command-quick-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/container-command-quick-menu.png
--------------------------------------------------------------------------------
/images/goto-process-definition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/goto-process-definition.png
--------------------------------------------------------------------------------
/images/include-process-command-quick-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/include-process-command-quick-menu.png
--------------------------------------------------------------------------------
/images/nfcore-sublime_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/nfcore-sublime_logo.png
--------------------------------------------------------------------------------
/images/params-popup-nf-core-viralrecon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/params-popup-nf-core-viralrecon.png
--------------------------------------------------------------------------------
/images/process-out-completion-nf-core-viralrecon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/process-out-completion-nf-core-viralrecon.png
--------------------------------------------------------------------------------
/images/process-out-popup-nf-core-viralrecon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/process-out-popup-nf-core-viralrecon.png
--------------------------------------------------------------------------------
/images/syntax-highlighting-module-imports-nf-core-viralrecon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/syntax-highlighting-module-imports-nf-core-viralrecon.png
--------------------------------------------------------------------------------
/images/syntax-highlighting-process-def-nf-core-viralrecon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/syntax-highlighting-process-def-nf-core-viralrecon.png
--------------------------------------------------------------------------------
/images/syntax-highlighting-workflow-def-nf-core-viralrecon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/syntax-highlighting-workflow-def-nf-core-viralrecon.png
--------------------------------------------------------------------------------
/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "1.0.0": "messages/1.0.0.txt",
3 | "install": "messages/install.txt"
4 | }
5 |
--------------------------------------------------------------------------------
/messages/1.0.0.txt:
--------------------------------------------------------------------------------
1 | => 1.0.0
2 |
3 | - Added syntax highlight for DSL-2
4 | - Added completions and commands for workflow `params`, `PROCESS.out.`, conda, container, module include
5 |
--------------------------------------------------------------------------------
/messages/1.1.0.txt:
--------------------------------------------------------------------------------
1 | => 1.1.0
2 |
3 | Added:
4 |
5 | - more informative popups for showing info about process output so it's easier to select the correct output channel without referencing the process code.
6 | - subworkflow completions and info popups about `take` and `emit` channels
7 | - `conda` directive snippet
8 |
9 | Fixed:
10 |
11 | - comment toggling
12 |
--------------------------------------------------------------------------------
/messages/1.2.0.txt:
--------------------------------------------------------------------------------
1 | => 1.2.0
2 |
3 | Added:
4 |
5 | - snippets for process version YML and ch_versions mixing (ver), process modules config (withName)
6 |
7 | Fixed:
8 |
9 | - process popups hanging due to bad regex for getting output emit labels (https://github.com/nf-core/sublime/issues/5)
10 |
--------------------------------------------------------------------------------
/messages/install.txt:
--------------------------------------------------------------------------------
1 | *********************************************************************************************
2 | * sublime-nextflow: Nextflow workflow syntax highlighting and snippets for Sublime Text 4 *
3 | *********************************************************************************************
4 |
5 | Thanks for using sublime-nextflow!
6 |
7 | If you want more info or have any issues, please see the Github page at:
8 |
9 | https://github.com/nf-core/sublime
10 |
--------------------------------------------------------------------------------
/nextflow_include_command.py:
--------------------------------------------------------------------------------
1 | import re
2 | from typing import Iterator
3 | import sys
4 | from pathlib import Path
5 |
6 | import sublime
7 | import sublime_plugin
8 |
9 |
10 | regex_process = re.compile(r'^process +(\w+) *\{\s*$')
11 | regex_functions = re.compile(r'^def +(\w+) *\([^\)]*\) *\{\s*$')
12 |
13 | def find_processes(path: Path) -> Iterator[str]:
14 | with open(path) as f:
15 | for l in f:
16 | m = regex_process.match(l)
17 | if m:
18 | yield m.group(1)
19 |
20 |
21 | def find_functions(path: Path) -> Iterator[str]:
22 | with open(path) as f:
23 | for l in f:
24 | m = regex_functions.match(l)
25 | if m:
26 | yield m.group(1)
27 |
28 |
29 | def relative_path(script_path: Path, import_path: Path) -> str:
30 | for i, parent_path in enumerate(script_path.parents):
31 | try:
32 | if i == 0:
33 | return './' + str(import_path.relative_to(parent_path))
34 | else:
35 | return '../'*i + str(import_path.relative_to(parent_path))
36 | except ValueError:
37 | continue
38 | return str(import_path)
39 |
40 |
41 | class NextflowIncludeInsertProcessCommand(sublime_plugin.TextCommand):
42 | def run(self, edit, **kwargs):
43 | process = kwargs.get('process', None)
44 | module_path = kwargs.get('module_path', None)
45 | if process and module_path:
46 | view = self.view
47 | if len(view.selection) > 1:
48 | return
49 | region = view.selection[0]
50 | point = region.a
51 | row, col = view.rowcol(point)
52 | if col != 0:
53 | return
54 | module_path = Path(module_path)
55 | module_path = module_path.parent / module_path.stem
56 | script_path = Path(view.file_name())
57 | view.insert(edit, point, "include { " + process + " } from '" + relative_path(script_path, module_path) + "' addParams( options: modules['" + process.lower() + "'] )")
58 |
59 |
60 | class NextflowIncludeProcessCommand(sublime_plugin.TextCommand):
61 | def run(self, edit, **kwargs):
62 | view = self.view
63 | if len(view.selection) > 1:
64 | return
65 | region = view.selection[0]
66 | point = region.a
67 | row, col = view.rowcol(point)
68 | if col != 0:
69 | return
70 | folders = view.window().folders()
71 | if not view.file_name():
72 | return
73 | if not folders:
74 | return
75 | root_dir = Path(folders[0])
76 | nf_files = [(proc, str(p.absolute())) for p in root_dir.rglob('*.nf') for proc in find_processes(p)]
77 |
78 | def on_select(x: int):
79 | if x == -1:
80 | return
81 | proc, nf_path = nf_files[x]
82 | root = str(root_dir.absolute())
83 | if nf_path.startswith(root):
84 | view.run_command(
85 | 'nextflow_include_insert_process',
86 | dict(process=proc,
87 | module_path=nf_path
88 | )
89 | )
90 |
91 | view.window().show_quick_panel(nf_files, on_select=on_select)
92 |
93 |
94 | class NextflowIncludeInsertFunctionsCommand(sublime_plugin.TextCommand):
95 | def run(self, edit, **kwargs):
96 | funcs = kwargs.get('funcs', None)
97 | module_path = kwargs.get('module_path', None)
98 | if funcs and module_path:
99 | view = self.view
100 | if len(view.selection) > 1:
101 | return
102 | region = view.selection[0]
103 | point = region.a
104 | row, col = view.rowcol(point)
105 | if col != 0:
106 | return
107 | module_path = Path(module_path)
108 | module_path = module_path.parent / module_path.stem
109 | script_path = Path(view.file_name())
110 | view.insert(edit, point, "include { " + '; '.join(funcs) + " } from '" + relative_path(script_path, module_path) + "'")
111 |
112 |
113 | class NextflowIncludeFunctionsCommand(sublime_plugin.TextCommand):
114 | def run(self, edit, **kwargs):
115 | view = self.view
116 | if len(view.selection) > 1:
117 | return
118 | region = view.selection[0]
119 | point = region.a
120 | row, col = view.rowcol(point)
121 | if col != 0:
122 | return
123 | folders = view.window().folders()
124 | if not view.file_name():
125 | return
126 | if not folders:
127 | return
128 | root_dir = Path(folders[0])
129 | nf_files = []
130 | for p in root_dir.rglob('*.nf'):
131 | abspath = str(p.absolute())
132 | funcs = list(find_functions(p))
133 | if funcs:
134 | nf_files.append((funcs, abspath))
135 |
136 | def on_select(x: int):
137 | if x == -1:
138 | return
139 | funcs, nf_path = nf_files[x]
140 | root = str(root_dir.absolute())
141 | if nf_path.startswith(root):
142 | view.run_command(
143 | 'nextflow_include_insert_functions',
144 | dict(funcs=funcs,
145 | module_path=nf_path
146 | )
147 | )
148 |
149 | view.window().show_quick_panel([('; '.join(x), y) for x,y in nf_files], on_select=on_select)
150 |
--------------------------------------------------------------------------------
/params_completions.py:
--------------------------------------------------------------------------------
1 | import re
2 | from typing import Iterator
3 | import sys
4 | from pathlib import Path
5 | import json
6 |
7 | import sublime
8 | import sublime_plugin
9 |
10 |
11 | regex_params = re.compile(
12 | r'\nparams\s*\{\n\s*(.*)',
13 | flags=re.MULTILINE | re.UNICODE | re.DOTALL
14 | )
15 | regex_param_val = re.compile(r'^(\w+)\s*=\s*(\S+).*$')
16 |
17 |
18 | def params_list(nf_config):
19 | params_match = regex_params.search(nf_config)
20 | if params_match:
21 | brackets = 1
22 | regex_param_val = re.compile(r'^(\w+)\s*=\s*(\S+).*$')
23 | param_val = []
24 | for l in params_match.group(1).split('\n'):
25 | l = l.strip()
26 | if not l or l.startswith('//'):
27 | continue
28 | m = regex_param_val.match(l)
29 | if m:
30 | param_val.append(m.groups())
31 | elif l.startswith('}'):
32 | brackets -= 1
33 | else:
34 | print("NOMATCH", l)
35 | if brackets == 0:
36 | break
37 | return param_val
38 | else:
39 | return None
40 |
41 |
42 | def get_param_info(nf_schema: dict, param: str) -> dict:
43 | for defn in nf_schema['definitions'].values():
44 | try:
45 | return defn['properties'][param]
46 | except KeyError:
47 | continue
48 | return {}
49 |
50 | def format_param_info(param_info: dict) -> str:
51 | param_type = param_info.get('type', 'string')
52 | default = param_info.get('default', '?')
53 | description = param_info.get('description', 'N/A')
54 | out = ''
55 | out += f'{description}
'
56 | out += (
57 | f'Type: {param_type}
'
58 | f'Default: {default}
'
59 | )
60 | if 'pattern' in param_info:
61 | out += f'Pattern: {param_info["pattern"]}
'
62 | if 'enum' in param_info:
63 | enum = param_info['enum']
64 | if isinstance(enum, list):
65 | enum = ', '.join(sorted(enum))
66 | out += f'
Enum: {enum}
'
67 | if 'help_text' in param_info:
68 | out += f'{param_info["help_text"]}
'
69 | return out
70 |
71 |
72 | class NextflowParamsEventListener(sublime_plugin.EventListener):
73 | def on_query_completions(self, view, prefix, locations):
74 | if view.syntax().name != 'Nextflow':
75 | return
76 | if len(locations) > 1:
77 | return
78 | point = locations[0]
79 | if not view.score_selector(point-1, 'source.nextflow punctuation.params.dot'):
80 | return
81 | folders = view.window().folders()
82 | if not folders:
83 | return
84 | root_dir = Path(folders[0])
85 | nf_config_path = root_dir / 'nextflow.config'
86 | if not nf_config_path.exists():
87 | print(f'Cannot get params completions. "{nf_config_path}" does not exist!')
88 | return None
89 |
90 | with open(nf_config_path) as f:
91 | nf_config = f.read()
92 | params_values = params_list(nf_config)
93 | if params_values:
94 | flags = sublime.INHIBIT_REORDER | sublime.INHIBIT_WORD_COMPLETIONS
95 | completions = sublime.CompletionList(
96 | completions=[
97 | sublime.CompletionItem(
98 | trigger=x,
99 | annotation=f'default: {y}',
100 | details=f'nextflow.config: params.{x} = {y}
'
101 | ) for x,y in params_values
102 | ],
103 | flags=flags
104 | )
105 | return completions
106 |
107 | def on_selection_modified_async(self, view):
108 | if view.syntax().name != 'Nextflow':
109 | return
110 | if len(view.selection) > 1:
111 | return
112 | region = view.selection[0]
113 | point = region.a
114 | if not view.score_selector(point, 'source.nextflow entity.name.parameter.nextflow'):
115 | return
116 | folders = view.window().folders()
117 | if not folders:
118 | return
119 | root_dir = Path(folders[0])
120 | nf_schema_path = root_dir / 'nextflow_schema.json'
121 | if not nf_schema_path.exists():
122 | return
123 |
124 | scope_region = view.extract_scope(point)
125 | param_text = view.substr(scope_region)
126 | with open(nf_schema_path) as f:
127 | nf_schema = json.load(f)
128 | param_info = get_param_info(nf_schema, param_text)
129 | view.show_popup(format_param_info(param_info))
130 |
--------------------------------------------------------------------------------
/process_label_completion.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import re
3 | from typing import Iterator, Tuple, List
4 | import sys
5 | from pathlib import Path
6 | import json
7 |
8 | import sublime
9 | import sublime_plugin
10 |
11 | regex_withlabel = re.compile(r'\s*withLabel\s*:\s*(?:[\'\"])?(\w+)(?:[\'\"])?\s*\{\s*')
12 |
13 |
14 | def get_config_labels(path: Path) -> List[Tuple[int, str, str]]:
15 | out = []
16 | text = path.read_text()
17 | for m in regex_withlabel.finditer(text):
18 | start, end = m.span()
19 | bracket_count = 1
20 | end_bracket = -1
21 | for i in range(end+1, len(text)):
22 | c = text[i]
23 | if c == '{':
24 | bracket_count += 1
25 | elif c == '}':
26 | bracket_count -= 1
27 | if bracket_count == 0:
28 | end_bracket = i
29 | break
30 | subtext = '\n'.join(x.strip() for x in text[end:end_bracket].strip().split('\n'))
31 | out.append((path.name, m.group(1), subtext))
32 | return out
33 |
34 |
35 | class NextflowProcessLabelEventListener(sublime_plugin.ViewEventListener):
36 | def on_query_completions(self, prefix, locations):
37 | view = self.view
38 | if view.syntax().name != 'Nextflow':
39 | return
40 | if len(locations) > 1:
41 | return
42 | point = locations[0]
43 | if not view.score_selector(point, 'source.nextflow meta.definition.process.nextflow'):
44 | return
45 | if not view.substr(view.line(point)).strip().startswith('label'):
46 | return
47 | folders = view.window().folders()
48 | if not folders:
49 | return
50 | root_dir = Path(folders[0])
51 | labels = []
52 | for path in root_dir.rglob('**/*.config'):
53 | labels += get_config_labels(path)
54 | if not labels:
55 | return
56 | flags = sublime.INHIBIT_REORDER | sublime.INHIBIT_WORD_COMPLETIONS
57 | completions = sublime.CompletionList(
58 | completions=[
59 | sublime.CompletionItem(
60 | trigger=f"'{label_name}'",
61 | annotation=f'{config_name}: {label_name}',
62 | details='|'.join(f'{x.replace(" ", "")}
' for x in text.split('\n'))
63 | ) for config_name, label_name, text in labels
64 | ],
65 | flags=flags
66 | )
67 | return completions
68 |
--------------------------------------------------------------------------------
/process_popups.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import re
4 | from pathlib import Path
5 | from typing import Tuple, List, Optional
6 |
7 | import sublime
8 | import sublime_plugin
9 |
10 | regex_input_section = re.compile(r'\s*input:\s*')
11 | regex_output_section = re.compile(r'\s*output:\s*')
12 | # regex to find output channels with emit
13 | regex_output_channel = re.compile(r'(.*?),\s*emit:\s*(\w+)', re.DOTALL)
14 |
15 | regex_take_section = re.compile(r'\s*take:\s*')
16 | regex_emit_section = re.compile(r'\s*emit:\s*')
17 | regex_wf_emit = re.compile(r'(\w+)\s*=\s*(.*)')
18 |
19 |
20 | def find_closing_bracket(text: str, start: int) -> int:
21 | count = 1
22 | for i in range(start, len(text)):
23 | c = text[i]
24 | if c == '{':
25 | count += 1
26 | elif c == '}':
27 | count -= 1
28 | if count == 0:
29 | return i
30 | return -1
31 |
32 |
33 | def find_process_name(proc_name: str, text: str) -> int:
34 | m = re.search(r'process\s+' + proc_name + r'\s*{', text)
35 | if m:
36 | return m.end()
37 | return -1
38 |
39 |
40 | def find_workflow_name(proc_name: str, text: str) -> int:
41 | m = re.search(r'workflow\s+' + proc_name + r'\s*{', text)
42 | if m:
43 | return m.end()
44 | return -1
45 |
46 |
47 | def find_proc_input_section(text: str, start: int, end: int) -> int:
48 | m = regex_input_section.search(text[start:end])
49 | if m:
50 | return m.end()
51 | return -1
52 |
53 |
54 | def find_wf_take_section(text: str, start: int, end: int) -> int:
55 | m = regex_take_section.search(text[start:end])
56 | if m:
57 | return m.end()
58 | return -1
59 |
60 |
61 | def get_input_channels(text: str, start: int, end: int) -> List[str]:
62 | out = []
63 | lines = text[start:end].split('\n')
64 | for line in lines:
65 | line = line.strip()
66 | if not line:
67 | continue
68 | if line.startswith('output:') or line.startswith('script:') or line.startswith('when:') or line.startswith(
69 | 'exec:'):
70 | break
71 | out.append(line)
72 | return out
73 |
74 |
75 | def get_wf_take_channels(text: str, start: int, end: int) -> List[str]:
76 | out = []
77 | lines = text[start:end].split('\n')
78 | for line in lines:
79 | line = line.strip()
80 | if not line:
81 | continue
82 | if line.startswith('main:'):
83 | break
84 | out.append(line)
85 | return out
86 |
87 |
88 | def get_input_channel_text(path: Path, proc_name: str) -> List[str]:
89 | text = path.read_text()
90 | proc_start = find_process_name(proc_name, text)
91 | if proc_start == -1:
92 | return []
93 | proc_end = find_closing_bracket(text, proc_start)
94 | if proc_end == -1:
95 | return []
96 | proc_input_section_start = find_proc_input_section(text, proc_start, proc_end)
97 | if proc_input_section_start == -1:
98 | return []
99 | proc_input_section_start += proc_start
100 | return get_input_channels(text, proc_input_section_start, proc_end)
101 |
102 |
103 | def get_wf_takes(path: Path, proc_name: str) -> List[str]:
104 | text = path.read_text()
105 | wf_start = find_workflow_name(proc_name, text)
106 | if wf_start == -1:
107 | return []
108 | wf_end = find_closing_bracket(text, wf_start)
109 | if wf_end == -1:
110 | return []
111 | wf_take_section_start = find_wf_take_section(text, wf_start, wf_end)
112 | if wf_take_section_start == -1:
113 | return []
114 | wf_take_section_start += wf_start
115 | return get_wf_take_channels(text, wf_take_section_start, wf_end)
116 |
117 |
118 | def proc_input_html(path: str, proc_name: str, input_channels_text: List[str]) -> str:
119 | out = f'
Process: {proc_name}
'
120 | out += f'File: {path}
'
121 | out += 'Input channels:
'
122 | for x in input_channels_text:
123 | out += f'{x}
'
124 | return out
125 |
126 |
127 | def find_proc_output_section(text: str, start: int, end: int) -> int:
128 | m = regex_output_section.search(text[start:end])
129 | if m:
130 | return m.end()
131 | return -1
132 |
133 |
134 | def find_wf_emit_section(text: str, start: int, end: int) -> int:
135 | m = regex_emit_section.search(text[start:end])
136 | if m:
137 | return m.end()
138 | return -1
139 |
140 |
141 | def get_output_channels(text: str, start: int, end: int) -> List[Tuple[str, str]]:
142 | out = []
143 | t = text[start:end]
144 | for m in regex_output_channel.finditer(t):
145 | chan, emit = m.groups()
146 | chan = ''.join(x.strip() for x in chan.split('\n'))
147 | out.append((emit, chan))
148 | return out
149 |
150 |
151 | def get_wf_emit_channels(text: str, start: int, end: int) -> List[Tuple[str, str]]:
152 | out = []
153 | for m in regex_wf_emit.finditer(text[start:end]):
154 | chan, emit = m.groups()
155 | out.append((chan, emit))
156 | return out
157 |
158 |
159 | def output_section_lines(text: str, start: int, end: int) -> List[Tuple[int, str]]:
160 | out = []
161 | lines = text[start:end].split('\n')
162 | for line in lines:
163 | line = line.strip()
164 | if not line:
165 | continue
166 | if line.startswith('script:') or line.startswith('when:') or line.startswith('exec:'):
167 | break
168 | out.append((len(out), line))
169 | return out
170 |
171 |
172 | def get_output_channel_emits(path: Path, proc_name: str) -> List[Tuple[str, str]]:
173 | text = path.read_text()
174 | proc_start = find_process_name(proc_name, text)
175 | if proc_start == -1:
176 | return []
177 | proc_end = find_closing_bracket(text, proc_start)
178 | if proc_end == -1:
179 | return []
180 | proc_output_start = find_proc_output_section(text, proc_start, proc_end)
181 | proc_output_start += proc_start
182 | next_section_match = re.search(r'(when:|exec:|script:|stub:)', text[proc_output_start:])
183 | if next_section_match:
184 | proc_end = proc_output_start + next_section_match.start()
185 | out = get_output_channels(text, proc_output_start, proc_end)
186 | if not out:
187 | out = output_section_lines(text, proc_output_start, proc_end)
188 | return out
189 |
190 |
191 | def get_wf_emits(path: Path, wf_name: str) -> List[Tuple[str, str]]:
192 | text = path.read_text()
193 | wf_start = find_workflow_name(wf_name, text)
194 | if wf_start == -1:
195 | return []
196 | wf_end = find_closing_bracket(text, wf_start)
197 | if wf_end == -1:
198 | return []
199 | wf_emit_start = find_wf_emit_section(text, wf_start, wf_end)
200 | if wf_emit_start == -1:
201 | return []
202 | wf_emit_start += wf_start
203 | out = get_wf_emit_channels(text, wf_emit_start, wf_end)
204 | if not out:
205 | out = output_section_lines(text, wf_emit_start, wf_end)
206 | return out
207 |
208 |
209 | def proc_output_html(path: str,
210 | proc_or_wf_name: str,
211 | output_channels_text: List[Tuple[str, str, Path]],
212 | focus_channel: str = None,
213 | is_proc: bool = True) -> str:
214 | out = f'{"Process" if is_proc else "Workflow"}: {proc_or_wf_name}
'
215 | out += f'File: {path}
'
216 | out += f'{"Output" if is_proc else "Emit"} channels:
'
217 | for emit, chan in output_channels_text:
218 | out += f''
219 | if focus_channel and focus_channel == emit:
220 | out += ''
221 | out += f'{emit}: {chan}
'
222 | if focus_channel and focus_channel == emit:
223 | out += ''
224 | out += f'
'
225 | return out
226 |
227 |
228 | def proc_info_html(
229 | path: str,
230 | proc_or_wf_name: str,
231 | input_channels_text: List[str],
232 | output_channels_text: List[Tuple[str, str, Path]],
233 | is_proc: bool = True
234 | ) -> str:
235 | out = f'{"Process" if is_proc else "Workflow"}: {proc_or_wf_name}
'
236 | out += f'File: {path}
'
237 | if input_channels_text:
238 | out += f'{"Input" if is_proc else "Take"} channels:
'
239 | for x in input_channels_text:
240 | out += f'{x}
'
241 | else:
242 | out += f'No {"input" if is_proc else "take"} channels for {"process" if is_proc else "workflow"}!'
243 | if output_channels_text:
244 | out += f'{"Output" if is_proc else "Emit"} channels:
'
245 | for emit, chan in output_channels_text:
246 | out += f'{emit}: {chan}
'
247 | else:
248 | out += f'No {"output" if is_proc else "emit"} channels for {"process" if is_proc else "workflow"}!'
249 | return out
250 |
251 |
252 | def show_proc_info(root_dir: Path, view: sublime.View, point: int) -> None:
253 | proc_name = view.substr(view.word(point))
254 | m, proc_name = find_import_proc_name(proc_name, view)
255 | path: Optional[Path] = None
256 | input_channels_text = []
257 | output_channels_text = []
258 | wfpath = Path(view.file_name())
259 | is_proc = True
260 | if m:
261 | nf_path = m.group(1) + '.nf'
262 | path = (wfpath.parent / nf_path).resolve()
263 | input_channels_text = get_input_channel_text(path, proc_name)
264 | if not input_channels_text:
265 | input_channels_text = get_wf_takes(path, proc_name)
266 | if input_channels_text:
267 | is_proc = False
268 | else:
269 | view.window().status_message(f'No input/take channels in {proc_name}!')
270 | output_channels_text = get_output_channel_emits(path, proc_name)
271 | if not output_channels_text:
272 | output_channels_text = get_wf_emits(path, proc_name)
273 | print(f'{output_channels_text=}')
274 | if output_channels_text:
275 | is_proc = False
276 | else:
277 | for path in root_dir.rglob('**/*.nf'):
278 | input_channels_text = get_input_channel_text(path, proc_name)
279 | output_channels_text = get_output_channel_emits(path, proc_name)
280 | if input_channels_text or output_channels_text:
281 | break
282 | if not input_channels_text and not output_channels_text:
283 | return
284 | if path is None:
285 | return
286 | short_path = str(path.absolute()).replace(str(root_dir.absolute()) + "/", "")
287 | view.show_popup(
288 | proc_info_html(
289 | path=short_path,
290 | proc_or_wf_name=proc_name,
291 | input_channels_text=input_channels_text,
292 | output_channels_text=output_channels_text,
293 | is_proc=is_proc,
294 | )
295 | )
296 |
297 |
298 | def find_import_proc_name(proc_name: str, view: sublime.View) -> Tuple[Optional[re.Match], str]:
299 | region = view.find(r'\w+ +as +' + proc_name, 0)
300 | if region.a != -1 and region.b != -1:
301 | proc_name = view.substr(view.word(sublime.Region(region.a, region.a)))
302 | include_str = view.substr(view.find(r'^include +\{\s*' + proc_name + r"\s*[^}]*\}\s+from\s+'[^']+'", 0))
303 | print(f'{include_str=}')
304 | m = re.match(r".*from\s+'\./([^']+)'", include_str)
305 | if m is None:
306 | m = re.match(r".*from\s+'([^']+)'", include_str)
307 | return m, proc_name
308 |
309 |
310 | def show_output_channel_popup(root_dir: Path, view: sublime.View, point: int) -> None:
311 | emit_or_out_word_region = view.word(point)
312 | emit_or_out_word_substr = view.substr(emit_or_out_word_region)
313 | focus_channel = None
314 | if emit_or_out_word_substr == 'out':
315 | proc_name = view.substr(view.word(emit_or_out_word_region.a - 2))
316 | else:
317 | out_word_region = view.word(emit_or_out_word_region.a - 2)
318 | out_word_substr = view.substr(out_word_region)
319 | if out_word_substr != 'out':
320 | print(f'Could not find "out" output channels keyword for process.')
321 | return
322 | focus_channel = emit_or_out_word_substr
323 | proc_name = view.substr(view.word(out_word_region.a - 2))
324 | window = view.window()
325 |
326 | m, proc_name = find_import_proc_name(proc_name, view)
327 | output_channels_text = []
328 | path: Optional[Path] = None
329 | wfpath = Path(view.file_name())
330 | is_proc: bool = True
331 | if m:
332 | nf_path = m.group(1) + '.nf'
333 | path = (wfpath.parent / nf_path).resolve()
334 | if not path.exists():
335 | paths = list(root_dir.rglob(nf_path))
336 | if not paths:
337 | view.window().status_message(f'No output channels in {proc_name}!')
338 | path = paths[0]
339 | output_channels_text = get_output_channel_emits(path, proc_name)
340 | if not output_channels_text:
341 | view.window().status_message(f'No output channels in {proc_name}!')
342 | else:
343 | output_channels_text = get_output_channel_emits(path, proc_name)
344 | if not output_channels_text:
345 | output_channels_text = get_wf_emits(path, proc_name)
346 | is_proc = False
347 | if not output_channels_text:
348 | window.status_message(f'No output channels in {proc_name}!')
349 | else:
350 | for path in root_dir.rglob('**/*.nf'):
351 | output_channels_text = get_output_channel_emits(path, proc_name)
352 | if output_channels_text:
353 | break
354 | if not output_channels_text or path is None:
355 | return
356 | short_path = str(path.absolute()).replace(str(root_dir.absolute()) + "/", "")
357 | view.show_popup(proc_output_html(short_path, proc_name, output_channels_text, focus_channel, is_proc))
358 |
359 |
360 | class NextflowWorkflowProcessCallEventListener(sublime_plugin.EventListener):
361 | def on_selection_modified_async(self, view: sublime.View):
362 | """Show popups for process calls and output channel property access
363 |
364 | When the cursor is navigated over:
365 |
366 | - a process name (entity.name.class.process.nextflow)
367 | - process out keyword (e.g. FASTQC.out) (keyword.process.out.nextflow)
368 | - named output channel (e.g. FASTQC.out.html) (variable.channel.process-output-emit.nextflow)
369 |
370 | A popup will be shown with info about the process input/output channels. For named output channels, the accessed output channel will be highlighted.
371 | """
372 | if view.syntax().name != 'Nextflow':
373 | return
374 | if len(view.selection) > 1:
375 | return
376 | folders = view.window().folders()
377 | if not folders:
378 | return
379 | root_dir = Path(folders[0])
380 | region = view.selection[0]
381 | point = region.a
382 | point_before = point - 1
383 | # if cursor on process name, then show popup with input/output channel info
384 | if view.score_selector(point,
385 | 'source.nextflow meta.definition.workflow.nextflow entity.name.class.process.nextflow'):
386 | show_proc_info(root_dir, view, point)
387 | return
388 | out_chan_scope = '(variable.channel.process-output-emit.nextflow | keyword.process.out.nextflow)'
389 | if view.score_selector(point, out_chan_scope) or view.score_selector(point - 1, out_chan_scope):
390 | show_output_channel_popup(root_dir, view, point)
391 | return
392 | # if cursor on or after `out` or named output channel, then show popup with process output channel info
393 | # highlighting named process
394 | proc_call_input_scope = 'source.nextflow meta.definition.workflow.nextflow meta.process-call.nextflow - (' \
395 | 'punctuation.accessor.dot.process-out.nextflow | entity | keyword | variable) '
396 | if not view.score_selector(point, proc_call_input_scope):
397 | return
398 | if not view.score_selector(point_before, proc_call_input_scope):
399 | return
400 | s = view.substr(point_before)
401 | if s not in (' ', ',', '('):
402 | return
403 | regions = [x for x in view.find_by_selector('meta.process-call.nextflow') if x.contains(point)]
404 | if not regions:
405 | return
406 | proc_call_str = view.substr(regions[0])
407 | m = re.match(r'^(\w+)', proc_call_str)
408 | if not m:
409 | return
410 | proc_name = m.group(1)
411 | m, proc_name = find_import_proc_name(proc_name, view)
412 | input_channels_text = []
413 | path: Optional[Path] = None
414 | wfpath = Path(view.file_name())
415 | if m:
416 | nf_path = m.group(1) + '.nf'
417 | path = (wfpath.parent / nf_path).resolve()
418 | if not path.exists():
419 | print(f'{path=} does not exist')
420 | paths = list(root_dir.rglob(nf_path))
421 | if len(paths) == 0:
422 | view.window().status_message(f'No input channels in {proc_name}!')
423 | path = paths[0]
424 | input_channels_text = get_input_channel_text(path, proc_name)
425 | if not input_channels_text:
426 | view.window().status_message(f'No input channels in {proc_name}!')
427 | else:
428 | input_channels_text = get_input_channel_text(path, proc_name)
429 | if not input_channels_text:
430 | view.window().status_message(f'No input channels in {proc_name}!')
431 | else:
432 | for path in root_dir.rglob('**/*.nf'):
433 | input_channels_text = get_input_channel_text(path, proc_name)
434 | if input_channels_text:
435 | break
436 | if not input_channels_text:
437 | return
438 | if path is None:
439 | return
440 | short_path = str(path.absolute()).replace(str(root_dir.absolute()), "")
441 | view.show_popup(proc_input_html(short_path, proc_name, input_channels_text))
442 |
443 | def on_query_completions(self, view, prefix, locations):
444 | window = view.window()
445 | if view.syntax().name != 'Nextflow':
446 | return
447 | if len(locations) > 1:
448 | return
449 | point = locations[0]
450 | if not view.score_selector(point - 1,
451 | 'source.nextflow meta.definition.workflow.nextflow punctuation.accessor.dot.process-out.nextflow'):
452 | return
453 | out_word_region = view.word(point - 2)
454 | if view.substr(out_word_region) != 'out':
455 | return
456 | proc_name_region = view.word(out_word_region.a - 1)
457 | proc_name = view.substr(proc_name_region)
458 | if not proc_name:
459 | return
460 |
461 | folders = view.window().folders()
462 | if not folders:
463 | return
464 | root_dir = Path(folders[0])
465 |
466 | m, proc_name = find_import_proc_name(proc_name, view)
467 | emits = []
468 | path = None
469 | wfpath = Path(view.file_name())
470 | if m:
471 | nf_path = m.group(1) + '.nf'
472 | path = (wfpath.parent / nf_path).resolve()
473 | if path.exists():
474 | emits = get_output_channel_emits(path, proc_name)
475 | if not emits:
476 | emits = get_wf_emits(path, proc_name)
477 | if not emits:
478 | window.status_message(f'No named output channels in {proc_name}!')
479 | else:
480 | for path in root_dir.rglob('**/*.nf'):
481 | emits = get_output_channel_emits(path, proc_name)
482 | if emits:
483 | break
484 | if not emits:
485 | return
486 | if path is None:
487 | return
488 | flags = sublime.INHIBIT_REORDER | sublime.INHIBIT_WORD_COMPLETIONS
489 | return sublime.CompletionList(
490 | completions=[
491 | sublime.CompletionItem(
492 | trigger=emit,
493 | annotation=chan,
494 | details=f'{chan}
From "{str(path.absolute()).replace(str(root_dir.absolute()) + "/", "")}"
',
495 | )
496 | for emit, chan in emits
497 | ],
498 | flags=flags,
499 | )
500 |
--------------------------------------------------------------------------------
/snippets/nextflow-ch_versions-mix.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | ver
6 | source.nextflow meta.definition.workflow.nextflow
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-conda.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | conda
6 | source.nextflow meta.definition.process.nextflow
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-done-log-oncomplete-onerror.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
20 | done
21 | source.nextflow - meta
22 |
23 |
--------------------------------------------------------------------------------
/snippets/nextflow-dsl2.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | dsl2
6 | source.nextflow - meta
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-env.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | !env
6 | source.nextflow - meta
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-log-info.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
7 | info
8 | source.nextflow
9 |
10 |
--------------------------------------------------------------------------------
/snippets/nextflow-modules-config.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 | filename.equals('versions.yml') ? null : filename }
10 | ]
11 | ]
12 | }
13 | $0
14 | ]]>
15 | withName
16 | source.nextflow meta.block.nextflow
17 |
18 |
--------------------------------------------------------------------------------
/snippets/nextflow-paired-end-illumina-reads-input-channel.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
8 | illumina
9 | source.nextflow - meta.definition.process.nextflow
10 |
11 |
--------------------------------------------------------------------------------
/snippets/nextflow-process.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 | \$output
13 | """
14 | }
15 | ]]>
16 | proc
17 | source.nextflow - meta
18 |
19 |
--------------------------------------------------------------------------------
/snippets/nextflow-publishDir.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
6 | pub
7 | source.nextflow meta.definition.process.nextflow
8 |
9 |
--------------------------------------------------------------------------------
/snippets/nextflow-script-cpus.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | cpus
6 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-script-memory-gigabytes.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | memg
6 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-script-memory-megabytes.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | memm
6 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-tag.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
5 | tag
6 | source.nextflow meta.definition.process.nextflow
7 |
8 |
--------------------------------------------------------------------------------
/snippets/nextflow-versions-yml.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 | versions.yml
4 | "\${task.process}":
5 | $1: \\\$($1 --version | sed 's/$1 //')$0
6 | END_VERSIONS
7 | ]]>
8 | ver
9 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow
10 |
11 |
--------------------------------------------------------------------------------
/syntax_test_.Nextflow:
--------------------------------------------------------------------------------
1 | // SYNTAX TEST "Packages/sublime-nextflow/Nextflow.sublime-syntax"
2 | #!/usr/bin/env nextflow
3 | // <- comment.line.hashbang.nextflow
4 |
5 | // <- source.nextflow
6 |
7 | include { SPADES as spades; IQTREE } from './modules/local/spades'
8 | // ^ meta.import-module.nextflow keyword.control.include-module.nextflow
9 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow punctuation.section.module-include-block.begin.nextflow
10 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow keyword.operator.as.nextflow
11 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow punctuation.separator.include-module-list.nextflow
12 | // ^ meta.import-module.nextflow keyword.control.include-module.from.nextflow
13 | // ^ meta.import-module.nextflow string.quoted.single.relative-import-path.nextflow punctuation.definition.string.begin.nextflow
14 | // ^ string.quoted.single.relative-import-path.nextflow punctuation.definition.relative-import-path.nextflow
15 | // ^ meta.import-module.nextflow string.quoted.single.relative-import-path.nextflow punctuation.definition.string.end.nextflow
16 | include { SPADES as spades; IQTREE } from '/modules/local/spades'
17 | // ^ invalid.illegal.relative-import-path.nextflow
18 |
19 | include { SPADES
20 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow invalid.illegal.unclosed-include-body.nextflow
21 |
22 | include { IQTREE } from '../modules/local/iqtree' addParams( options: modules['iqtree'] )
23 |
24 | params.reads_dir = "$baseDir/data/reads/"
25 | // ^ support.variable.params.nextflow
26 | // ^ entity.name.parameter.nextflow
27 |
28 | params.cdhit_identity_threshold = 0.9
29 | // ^ keyword.operator.assignment.nextflow
30 | // ^ constant.numeric.nextflow
31 |
32 | // <- source.nextflow
33 |
34 | // Show help message if --help specified
35 | // ^ source.nextflow comment.line.double-slash.nextflow
36 | if (params.help){
37 | // <- keyword.control.nextflow
38 | // ^ support.variable.params.nextflow
39 | // ^ entity.name.parameter.nextflow
40 | helpMessage()
41 | // ^ meta.block.nextflow meta.method-call.nextflow meta.method.nextflow
42 | exit 0
43 | // ^ meta.block.nextflow support.function.exit.nextflow
44 | // ^ meta.block.nextflow constant.numeric.nextflow
45 | }
46 |
47 | Channel
48 | // <- storage.type.class.nextflow
49 | .fromFilePairs(params.input)
50 | // ^ support.function.channel-factory.nextflow
51 | .map { [ it[0].replaceAll(/_S\d{1,2}_L001/, ""), it[1] ] }
52 | // ^ keyword.operator.channel-transforming-operator.nextflow
53 | // ^ meta.block.nextflow meta.structure.nextflow meta.structure.nextflow constant.numeric.nextflow
54 | // ^ meta.block.nextflow meta.structure.nextflow meta.method-call.nextflow meta.method.nextflow
55 | // ^ variable.language.nextflow
56 | // ^ meta.block.nextflow meta.structure.nextflow meta.method-call.nextflow string.regexp.nextflow
57 | .set { ch_input }
58 | // ^ keyword.operator.channel-other-operator.nextflow
59 |
60 | // <- source.nextflow
61 |
62 | log.info """
63 | // <- support.variable.log.nextflow
64 | ${workflow.commandLine}
65 | // ^ source.nextflow.embedded.source support.variable.workflow.nextflow
66 | """
67 |
68 | // <- source.nextflow
69 |
70 | process download_phix {
71 | // ^ meta.definition.process.nextflow storage.type.return-type.def.nextflow
72 | // ^ meta.definition.process.nextflow entity.name.process.nextflow
73 | // ^ meta.definition.process.nextflow punctuation.definition.process.begin.nextflow
74 | output:
75 | file('phix.fa') into phix_file
76 | // ^ meta.definition.process.nextflow meta.definition.process-output.nextflow support.function.file.nextflow
77 | // ^ meta.definition.process-output.nextflow string.quoted.single.nextflow
78 | // ^ meta.definition.process-output.nextflow keyword.operator.into.nextflow
79 |
80 | '''
81 | // ^ meta.definition.process.nextflow string.quoted.single.block.nextflow punctuation.definition.string.begin.nextflow
82 | curl "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=nuccore&id=NC_001422.1&rettype=fasta&retmode=text" > phix.fa
83 | // ^ meta.definition.process.nextflow string.quoted.single.block.nextflow
84 | '''
85 | // ^ meta.definition.process.nextflow string.quoted.single.block.nextflow punctuation.definition.string.end.nextflow
86 | }
87 |
88 | // <- source.nextflow
89 |
90 | process remove_phix {
91 | // <- meta.definition.process.nextflow
92 | // <- storage.type.return-type.def.nextflow
93 | // ^ entity.name.process.nextflow
94 | // ^ meta.definition.process.nextflow punctuation.definition.process.begin.nextflow
95 | tag "$sample_id"
96 | // ^ meta.definition.process.nextflow support.type.nextflow
97 | // ^ string.quoted.double.nextflow variable.other.interpolated.nextflow
98 |
99 | input:
100 | // ^ meta.definition.process.nextflow meta.definition.process-input.nextflow keyword.other.process-input.nextflow
101 | file phix from phix_file
102 | // ^ meta.definition.process-input.nextflow support.function.file.nextflow
103 | // ^ meta.definition.process-input.nextflow keyword.operator.from.nextflow
104 | set sample_id, file(read1), file(read2) from samples_ch
105 | // ^ meta.definition.process-input.nextflow keyword.other.channel.set.nextflow
106 | // ^ meta.definition.process-input.nextflow support.function.file.nextflow
107 | // ^ meta.definition.process-input.nextflow keyword.operator.from.nextflow
108 | output:
109 | // ^ meta.definition.process.nextflow meta.definition.process-output.nextflow
110 | set val(sample_id), file(filt_reads1), file(filt_reads2) into filtered_reads_ch, filtered_reads_ch2
111 | // ^ meta.definition.process-output.nextflow keyword.other.channel.set.nextflow
112 | // ^ meta.definition.process-output.nextflow support.function.val.nextflow
113 | // ^ meta.definition.process-output.nextflow support.function.file.nextflow
114 | // ^ meta.definition.process-output.nextflow keyword.operator.into.nextflow
115 | file stats into remove_phix_stats_ch
116 |
117 | script:
118 | // ^ meta.definition.process.nextflow keyword.other.process-script-def.nextflow
119 | filt_reads1 = "${sample_id}_1.fq"
120 | filt_reads2 = "${sample_id}_2.fq"
121 | stats = "${sample_id}-remove_phix-stats.txt"
122 | """
123 | // ^ meta.definition.process.nextflow string.quoted.double.block.nextflow punctuation.definition.string.begin.nextflow
124 | bbduk.sh in1=${read1} in2=${read2} out1=${filt_reads1} out2=${filt_reads2} ref=$phix k=31 hdist=1 stats=${stats}
125 | // ^ meta.definition.process.nextflow string.quoted.double.block.nextflow
126 | // ^ string.quoted.double.block.nextflow source.nextflow.embedded.source
127 | // ^ string.quoted.double.block.nextflow variable.other.interpolated.nextflow
128 | """
129 | // ^ meta.definition.process.nextflow string.quoted.double.block.nextflow punctuation.definition.string.end.nextflow
130 | }
131 |
132 | process BLAST {
133 | conda 'bioconda::blast=2.11.0=pl526he19e7b1_0'
134 | // ^ meta.definition.conda-directive.nextflow support.type.conda.nextflow
135 | // ^ meta.definition.conda-directive.nextflow string.quoted.single.nextflow punctuation.definition.string.begin.nextflow
136 |
137 | input:
138 | tuple val(meta), path(fasta)
139 | // ^ meta.definition.process.nextflow meta.definition.process-input.nextflow keyword.other.channel.tuple.nextflow
140 | // ^ meta.definition.process-input.nextflow support.function.val.nextflow
141 | // ^ meta.definition.process-input.nextflow support.function.path.nextflow
142 | path(db)
143 | // ^ meta.definition.process-input.nextflow support.function.path.nextflow
144 |
145 | output:
146 | tuple val(meta), path("*.blast.txt"), optional: true, emit: txt
147 | // ^ meta.definition.process.nextflow meta.definition.process-output.nextflow keyword.other.channel.tuple.nextflow
148 | // ^ meta.definition.process-output.nextflow support.function.val.nextflow
149 | // ^ meta.definition.process-output.nextflow support.function.path.nextflow
150 | path('*.version.txt'), emit: version
151 | path('file')
152 |
153 | script:
154 | """
155 | blastn -db $db -query $fasta -outfmt 6 -out ${meta.id}.blast.txt
156 |
157 | blastn -version > blast.version.txt
158 | """
159 | }
160 |
161 |
162 | // <- source.nextflow
163 |
164 | process MASH_SCREEN {
165 | tag "${meta.id}"
166 | publishDir "$outdir/mash_screen", mode: params.publish_dir_mode
167 | // ^ meta.definition.process.nextflow support.type.nextflow
168 | // ^ meta.definition.process.nextflow constant.other.key.nextflow
169 |
170 | input:
171 | path db
172 | // ^ meta.definition.process.nextflow meta.definition.process-input.nextflow meta.function-call.process-path.nextflow variable.other.process.nextflow
173 | tuple val(meta), path(reads)
174 |
175 | output:
176 | tuple val(meta), path('*.mash_screen.tsv'), emit: results
177 | // ^ meta.definition.process-output.nextflow meta.definition.process-output-channel.nextflow meta.function-call.process-val.nextflow support.function.val.nextflow
178 | // ^ meta.definition.process-output.nextflow meta.definition.process-output-channel.nextflow meta.function-call.process-val.nextflow variable.other.process.nextflow
179 | path '*.version.txt', emit: version
180 |
181 | """
182 | echo "identity\tmatching_sketches\tmedian_multiplicity\tp_value\tquery_id\tquery_comment" > ${meta.id}.mash_screen.tsv
183 | mash screen -w -p ${task.cpus} $db $reads | sort -grk1 - >> ${meta.id}.mash_screen.tsv
184 |
185 | mash --version > mash.version.txt
186 | """
187 | }
188 |
189 | process ProcessName {
190 | tag "info about process"
191 |
192 | input:
193 | set val(value), file(file1) into ch_files
194 | // ^ source.nextflow meta.definition.process.nextflow meta.definition.process-input.nextflow invalid.illegal.into.nextflow
195 |
196 | output:
197 | set val(value), file(output) from ch_output
198 | // ^ source.nextflow meta.definition.process.nextflow meta.definition.process-output.nextflow invalid.illegal.from.nextflow
199 |
200 | script:
201 | """
202 | dostuff
203 | """
204 | }
205 |
206 |
207 | Channel.empty() // Create assemblies channel
208 | // ^ support.function.channel-factory.nextflow
209 | // ^ comment.line.double-slash.nextflow
210 | .mix(shovill_assembly_ch, unicycler_assembly_ch)
211 | // ^ keyword.operator.channel-combining-operator.nextflow
212 | .set { assembly_ch }
213 | // ^ keyword.operator.channel-other-operator.nextflow
214 |
215 | // <- source.nextflow
216 |
217 |
218 | workflow {
219 | // ^ meta.definition.workflow.nextflow keyword.declaration.workflow.nextflow
220 | ch_input = Channel.fromPath(params.input)
221 | SPADES(ch_input, params.spades_args)
222 | // ^ meta.process-call.nextflow entity.name.class.process.nextflow
223 | // ^ meta.process-call.nextflow punctuation.section.process-call.begin.nextflow
224 | // ^ meta.process-call.nextflow punctuation.section.arguments.end.nextflow
225 | // ^ source.nextflow meta.definition.workflow.nextflow meta.process-call.nextflow variable.parameter.channel.nextflow
226 | ch_db = Channel.fromPath(params.blast_db)
227 | BLAST(SPADES.out.txt, ch_db)
228 | // ^ meta.process-call.nextflow punctuation.section.process-call.begin.nextflow
229 | // ^ meta.process-call.nextflow variable.channel.process-output-emit.nextflow
230 | if (!params.skip_kraken2) {
231 | ch_index = Channel.fromPath(params.kraken2_index)
232 | KRAKEN2(ch_index, ch_input)
233 | // ^ meta.conditional-block.nextflow meta.process-call.nextflow entity.name.class.process.nextflow
234 | } else {
235 | ch_index = Channel.fromPath(params.kraken2_index)
236 | KRAKEN2(ch_index, ch_input)
237 | // ^ meta.conditional-block.nextflow meta.process-call.nextflow entity.name.class.process.nextflow
238 | }
239 | WHATEVER(ch_input)
240 | // ^ meta.process-call.nextflow entity.name.class.process.nextflow
241 | }
242 |
243 | workflow DOSTUFF {
244 | // ^ meta.definition.workflow.nextflow entity.name.workflow.nextflow
245 | }
246 |
247 |
248 | workflow.onComplete {
249 | // ^ support.variable.workflow.nextflow
250 | println """
251 | // ^ meta.block.nextflow support.function.print.nextflow
252 | Completed at : ${workflow.complete}
253 | // ^ meta.block.nextflow string.quoted.double.block.nextflow source.nextflow.embedded.source support.variable.workflow.nextflow
254 | Results Dir : ${file(params.outdir)}
255 | // ^ source.nextflow.embedded.source support.function.file.nextflow
256 | // ^ support.variable.params.nextflow
257 | // ^ entity.name.parameter.nextflow
258 | """.stripIndent()
259 | // ^ meta.method-call.nextflow meta.method.nextflow
260 | }
261 |
--------------------------------------------------------------------------------