def route_home(self, path): config = get_project_config() self.render_response( 200, 'home.html', { 'title': config.get('project', 'category'), 'data_files': load_json_files(config) })
def fetch_cmd(cfg, parser, parsed, args): if not parsed.quiet: parser.print_byline() projectpath = cfg.get('project', 'projectpath') jsonpath = os.path.join(projectpath, 'json') # source_url -> filename source_map = dict( (item['source_url'], fn) for fn, item in load_json_files(cfg) ) if not os.path.exists(jsonpath): os.makedirs(jsonpath) try: url = cfg.get('project', 'url') except ConfigParser.NoOptionError: url = '' if not url: err('url not specified in steve.ini project config file.') err('Add "url = ..." to [project] section of steve.ini file.') return 1 if 'youtube' in url: try: youtube_embed = YOUTUBE_EMBED[cfg.get('project', 'youtube_embed')] except KeyError: err('youtube_embed must be either "iframe" or "object".') return 1 else: youtube_embed = None out('Scraping {0}...'.format(url)) videos = fetch_videos_from_url(url, youtube_embed) print 'Found {0} videos...'.format(len(videos)) for i, video in enumerate(videos): if video['source_url'] in source_map and not parsed.force: print 'Skipping {0}... already exists.'.format( stringify(video['title'])) continue filename = generate_filename(video['title']) filename = '{index:04d}_{basename}.json'.format( index=i, basename=filename[:40]) print 'Working on {0}... ({1})'.format( stringify(video['title']), filename) f = open(os.path.join('json', filename), 'w') f.write(convert_to_json(video)) f.close() # TODO: what if there's a file there already? on the first one, # prompt the user whether to stomp on existing files or skip. return 0
def fetch_cmd(cfg, parser, parsed, args): if not parsed.quiet: parser.print_byline() projectpath = cfg.get('project', 'projectpath') jsonpath = os.path.join(projectpath, 'json') # source_url -> filename source_map = dict( (item['source_url'], fn) for fn, item in load_json_files(cfg)) if not os.path.exists(jsonpath): os.makedirs(jsonpath) try: url = cfg.get('project', 'url') except ConfigParser.NoOptionError: url = '' if not url: err('url not specified in steve.ini project config file.') err('Add "url = ..." to [project] section of steve.ini file.') return 1 if 'youtube' in url: try: youtube_embed = YOUTUBE_EMBED[cfg.get('project', 'youtube_embed')] except KeyError: err('youtube_embed must be either "iframe" or "object".') return 1 else: youtube_embed = None out('Scraping {0}...'.format(url)) videos = fetch_videos_from_url(url, youtube_embed) print 'Found {0} videos...'.format(len(videos)) for i, video in enumerate(videos): if video['source_url'] in source_map and not parsed.force: print 'Skipping {0}... already exists.'.format( stringify(video['title'])) continue filename = generate_filename(video['title']) filename = '{index:04d}_{basename}.json'.format(index=i, basename=filename[:40]) print 'Working on {0}... ({1})'.format(stringify(video['title']), filename) f = open(os.path.join('json', filename), 'w') f.write(convert_to_json(video)) f.close() # TODO: what if there's a file there already? on the first one, # prompt the user whether to stomp on existing files or skip. return 0
def fetch(cfg, ctx, quiet, force): """Fetches videos and generates JSON files.""" if not quiet: click.echo(VERSION) jsonpath = cfg.get('project', 'jsonpath') # source_url -> filename source_map = dict( (item['source_url'], fn) for fn, item in load_json_files(cfg) ) if not os.path.exists(jsonpath): os.makedirs(jsonpath) try: url = cfg.get('project', 'url') except ConfigParser.NoOptionError: url = '' if not url: raise click.ClickException( u'url not specified in {0} project config file.\n\n' u'Add "url = ..." to [project] section of {0} file.'.format( get_project_config_file_name()) ) click.echo(u'Scraping {0}...'.format(url)) click.echo(u'(This can take a *long* time with no indication of progress.)') videos = scrape_videos(url) click.echo(u'Found {0} videos...'.format(len(videos))) for i, video in enumerate(videos): if video['source_url'] in source_map and not force: click.echo(u'Skipping {0}... already exists.'.format( stringify(video['title']))) continue filename = generate_filename(video['title']) filename = '{index:04d}_{basename}.json'.format( index=i, basename=filename[:40]) click.echo(u'Created {0}... ({1})'.format( stringify(video['title']), filename)) with open(os.path.join(jsonpath, filename), 'w') as fp: fp.write(convert_to_json(video))
def fetch(cfg, ctx, quiet, force): """Fetches videos and generates JSON files.""" if not quiet: click.echo(VERSION) jsonpath = cfg.get('project', 'jsonpath') # source_url -> filename source_map = dict( (item['source_url'], fn) for fn, item in load_json_files(cfg) ) if not os.path.exists(jsonpath): os.makedirs(jsonpath) try: url = cfg.get('project', 'url') except NoOptionError: url = '' if not url: raise click.ClickException( u'url not specified in {0} project config file.\n\n' u'Add "url = ..." to [project] section of {0} file.'.format( get_project_config_file_name()) ) click.echo(u'Scraping {0}...'.format(url)) click.echo(u'(This can take a *long* time with no indication of progress.)') videos = scrape_videos(url) click.echo(u'Found {0} videos...'.format(len(videos))) for i, video in enumerate(videos): if video['source_url'] in source_map and not force: click.echo(u'Skipping {0}... already exists.'.format( stringify(video['title']))) continue filename = generate_filename(video['title']) filename = '{index:04d}_{basename}.json'.format( index=i, basename=filename[:40]) click.echo(u'Created {0}... ({1})'.format( stringify(video['title']), filename)) with open(os.path.join(jsonpath, filename), 'w') as fp: fp.write(convert_to_json(video))
def status_cmd(cfg, parser, parsed, args): if not parsed.quiet and not parsed.list: parser.print_byline() if not parsed.list and not parsed.quiet: out('Video status:') out('') files = load_json_files(cfg) if not files: if not parsed.list: out('No files') return 0 term = blessings.Terminal() done_files = [] in_progress_files = [] for fn, contents in files: whiteboard = contents.get('whiteboard', '') if whiteboard: in_progress_files.append((fn, whiteboard)) else: done_files.append(fn) if parsed.list: for fn in in_progress_files: out(fn, wrap=False) else: if in_progress_files: for fn, whiteboard in in_progress_files: out(u'{0}: {1}'.format(fn, term.bold(whiteboard)), wrap=False) if done_files: out('') for fn in done_files: out('{0}: {1}'.format(fn, term.bold(term.green('Done!'))), wrap=False) out('') out('In progress: {0:3d}'.format(len(in_progress_files))) out('Done: {0:3d}'.format(len(done_files))) return 0
def route_edit(self, path): cfg = get_project_config() data_file = get_data(cfg, path[1]) if not data_file: return self.render_error(404) fn, data = data_file reqs = get_video_requirements() # TODO: verify the data and add the errors to the fields? all_files = [filename for filename, _ in load_json_files(cfg)] fn_index = all_files.index(fn) prev_fn = all_files[fn_index - 1] if fn_index > 0 else '' next_fn = (all_files[fn_index + 1] if fn_index < len(all_files) - 1 else '') fields = [] category = cfg.get('project', 'category') for req in reqs: key = req['name'] if key == 'category' and category: fields.append( { 'name': req['name'], 'value': category }) else: fields.append( { 'name': req['name'], 'type': req['type'], 'choices': req['choices'], 'md': req['md'], 'value': data.get(key, '') }) self.render_response( 200, 'edit.html', { 'title': u'edit {0}'.format(data['title']), 'fn': fn, 'fields': fields, 'prev_fn': prev_fn, 'next_fn': next_fn })
def status(cfg, ctx, quiet, aslist): """Shows status for all videos in this project.""" quiet = quiet or aslist if not quiet: click.echo(VERSION) click.echo('Video status:') click.echo() files = load_json_files(cfg) if not files: if not aslist: click.echo('No files') return done_files = [] in_progress_files = [] for fn, contents in files: whiteboard = contents.get('whiteboard', '') if whiteboard: in_progress_files.append((fn, whiteboard)) else: done_files.append(fn) if aslist: for fn in in_progress_files: click.echo(fn) else: table = [] if in_progress_files: for fn, whiteboard in in_progress_files: table.append([fn, whiteboard]) if done_files: for fn in done_files: table.append([fn, 'Done!']) click.echo(tabulate.tabulate(table)) click.echo() click.echo('In progress: {0:4d}'.format(len(in_progress_files))) click.echo('Done: {0:4d}'.format(len(done_files)))
def route_edit(self, path): cfg = get_project_config() data_file = get_data(cfg, path[1]) if not data_file: return self.render_error(404) fn, data = data_file reqs = get_video_requirements() # TODO: verify the data and add the errors to the fields? all_files = [filename for filename, _ in load_json_files(cfg)] fn_index = all_files.index(fn) prev_fn = all_files[fn_index - 1] if fn_index > 0 else '' next_fn = (all_files[fn_index + 1] if fn_index < len(all_files) - 1 else '') fields = [] category = cfg.get('project', 'category') for req in reqs: key = req['name'] if key == 'category' and category: fields.append({'name': req['name'], 'value': category}) else: fields.append({ 'name': req['name'], 'type': req['type'], 'choices': req['choices'], 'md': req['md'], 'value': data.get(key, '') }) self.render_response( 200, 'edit.html', { 'title': u'edit {0}'.format(data['title']), 'fn': fn, 'fields': fields, 'prev_fn': prev_fn, 'next_fn': next_fn })
def verify(cfg, ctx, quiet): """Verifies JSON data.""" if not quiet: click.echo(VERSION) files = load_json_files(cfg) if not files: click.echo('No files') return filename_to_errors = verify_json_files( files, cfg.get('project', 'category')) for filename in sorted(filename_to_errors.keys()): errors = filename_to_errors[filename] if errors: click.echo(filename) for error in errors: click.echo(' - {0}'.format(error)) click.echo('Done!')
def verify_cmd(cfg, parser, parsed, args): if not parsed.quiet: parser.print_byline() files = load_json_files(cfg) if not files: out('No files') return 0 filename_to_errors = verify_json_files(files, cfg.get('project', 'category')) for filename in sorted(filename_to_errors.keys()): errors = filename_to_errors[filename] if errors: out(filename) for error in errors: out(' - {0}'.format(error)) out('Done!') return 0
def verify_cmd(cfg, parser, parsed, args): if not parsed.quiet: parser.print_byline() files = load_json_files(cfg) if not files: out('No files') return 0 filename_to_errors = verify_json_files( files, cfg.get('project', 'category')) for filename in sorted(filename_to_errors.keys()): errors = filename_to_errors[filename] if errors: out(filename) for error in errors: out(' - {0}'.format(error)) out('Done!') return 0
def push_cmd(cfg, parser, parsed, args): if not parsed.quiet: parser.print_byline() # Get username, api_url and api_key. username = get_from_config(cfg, 'username') api_url = get_from_config(cfg, 'api_url') update = parsed.update # Command line api_key overrides config-set api_key api_key = parsed.apikey if not api_key: try: api_key = cfg.get('project', 'api_key') except ConfigParser.NoOptionError: pass if not api_key: err('Specify an api key either in steve.ini, on command line, ' 'or in API_KEY file.') return 1 if not username or not api_url or not api_key: return 1 data = load_json_files(cfg) if args: data = [(fn, contents) for fn, contents in data if fn in args] # There are two modes: # # 1. User set category in configuration. Then the json files can # either have no category set or they have to have the same # category set. # # 2. User has NOT set category in configuration. Then the json # files must all have the category set. The categories can be # different. # # Go through and make sure there aren't any problems with # categories. api = steve.restapi.API(api_url) all_categories = dict( [(cat['title'], cat) for cat in steve.richardapi.get_all_categories(api_url)]) try: category = cfg.get('project', 'category') category = category.strip() if category not in all_categories: err('Category "{0}" does not exist on server. Build it there ' 'first.'.format(category)) return 1 except ConfigParser.NoOptionError: category = None errors = False for fn, contents in data: if category is None: this_cat = contents.get('category') if not this_cat: err('No category set in configuration and {0} has no ' 'category set.'.format(fn)) errors = True elif this_cat != this_cat.strip(): err('Category "{0}" has whitespace at beginning or ' 'end.'.format(this_cat)) return 1 elif this_cat not in all_categories: err('Category "{0}" does not exist on server. ' 'Build it there first.'.format(this_cat)) return 1 else: this_cat = contents.get('category') if this_cat is not None and str(this_cat).strip() != category: err('Category set in configuration ({0}), but {1} has ' 'different category ({2}).'.format( category, fn, this_cat)) errors = True if update: for fn, contents in data: if not 'id' in contents: err('id not in contents for "{0}".'.format(fn)) errors = True break if errors: err('Aborting.') return 1 # Everything looks ok. So double-check with the user and push. out('Pushing to: {0}'.format(api_url)) out('Username: {0}'.format(username)) out('api_key: {0}'.format(api_key)) out('update?: {0}'.format(update)) out('Once you push, you can not undo it. Push for realz? Y/N') if not raw_input().strip().lower().startswith('y'): err('Aborting.') return 1 for fn, contents in data: contents['category'] = category or contents.get('category') if not update: # Nix any id field since that causes problems. if 'id' in contents: del contents['id'] out('Pushing {0}'.format(fn)) try: vid = steve.restapi.get_content( api.video.post(contents, username=username, api_key=api_key)) if 'id' in vid: contents['id'] = vid['id'] out(' Now has id {0}'.format(vid['id'])) else: err(' Errors?: {0}'.format(vid)) except steve.restapi.RestAPIException as exc: err(' Error?: {0}'.format(exc)) err(' "{0}"'.format(exc.response.content)) else: out('Updating {0} "{1}" ({2})'.format( contents['id'], contents['title'], fn)) try: vid = steve.restapi.get_content( api.video(contents['id']).put( contents, username=username, api_key=api_key)) except steve.restapi.RestAPIException as exc: err(' Error?: {0}'.format(exc)) err(' "{0}"'.format(exc.response.content)) save_json_file(cfg, fn, contents) return 0
def push(cfg, ctx, quiet, apikey, update, overwrite, files): """Pushes metadata to a richard instance.""" if not quiet: click.echo(VERSION) # Get username, api_url and api_key. username = get_from_config(cfg, 'username') api_url = get_from_config(cfg, 'api_url') # Command line api_key overrides config-set api_key if not apikey: try: apikey = cfg.get('project', 'api_key') except ConfigParser.NoOptionError: pass if not apikey: raise click.ClickException( u'Specify an api key either in {0}, on command line, ' u'or in API_KEY file.'.format(get_project_config_file_name()) ) if not username or not api_url or not apikey: raise click.ClickException(u'Missing username, api_url or api_key.') data = load_json_files(cfg) if files: data = [(fn, contents) for fn, contents in data if fn in files] # There are two modes: # # 1. User set category in configuration. Then the json files can # either have no category set or they have to have the same # category set. # # 2. User has NOT set category in configuration. Then the json # files must all have the category set. The categories can be # different. # # Go through and make sure there aren't any problems with # categories. all_categories = dict( [(cat['title'], cat) for cat in steve.richardapi.get_all_categories(api_url)]) try: category = cfg.get('project', 'category') category = category.strip() if category not in all_categories: raise click.ClickException( u'Category "{0}" does not exist on server. Build it there ' u'first.'.format(category) ) else: click.echo('Category {0} exists on site.'.format(category)) except ConfigParser.NoOptionError: category = None errors = [] for fn, contents in data: if category is None: this_cat = contents.get('category') if not this_cat: errors.append( u'No category set in configuration and {0} has no ' u'category set.'.format(fn) ) elif this_cat != this_cat.strip(): errors.append( u'Category "{0}" has whitespace at beginning or ' u'end.'.format(this_cat) ) elif this_cat not in all_categories: errors.append( u'Category "{0}" does not exist on server. ' u'Build it there first.'.format(this_cat) ) else: this_cat = contents.get('category') if this_cat is not None and str(this_cat).strip() != category: errors.append( u'Category set in configuration ({0}), but {1} has ' u'different category ({2}).'.format(category, fn, this_cat) ) if update: for fn, contents in data: if 'id' not in contents: errors.append( u'id not in contents for "{0}".'.format(fn) ) if errors: raise click.ClickException('\n'.join(errors)) # Everything looks ok. So double-check with the user and push. click.echo('Pushing to: {0}'.format(api_url)) click.echo('Username: {0}'.format(username)) click.echo('api_key: {0}'.format(apikey)) click.echo('update?: {0}'.format(update)) click.echo('# videos: {0}'.format(len(data))) click.echo('Once you push, you can not undo it. Push for realz? Y/N') if not raw_input().strip().lower().startswith('y'): raise click.Abort() for fn, contents in data: contents['category'] = category or contents.get('category') if not update: # Nix any id field since that causes problems. if 'id' in contents: if not overwrite: click.echo(u'Skipping... already exists.') continue del contents['id'] click.echo('Pushing {0}'.format(fn)) try: vid = steve.richardapi.create_video(api_url, apikey, contents) if 'id' in vid: contents['id'] = vid['id'] click.echo(' Now has id {0}'.format(vid['id'])) else: click.echo(' Errors?: {0}'.format(vid), err=True) except steve.restapi.RestAPIException as exc: click.echo(' Error?: {0}'.format(exc), err=True) click.echo(' "{0}"'.format(exc.response.content), err=True) else: click.echo('Updating {0} "{1}" ({2})'.format( contents['id'], contents['title'], fn)) try: vid = steve.richardapi.update_video( api_url, apikey, contents['id'], contents) except steve.restapi.RestAPIException as exc: click.echo(' Error?: {0}'.format(exc), err=True) click.echo(' "{0}"'.format(exc.response.content), err=True) save_json_file(cfg, fn, contents)
def push(cfg, ctx, quiet, apikey, update, overwrite, files): """Pushes metadata to a richard instance.""" if not quiet: click.echo(VERSION) # Get username, api_url and api_key. username = get_from_config(cfg, 'username') api_url = get_from_config(cfg, 'api_url') # Command line api_key overrides config-set api_key if not apikey: try: apikey = cfg.get('project', 'api_key') except NoOptionError: pass if not apikey: raise click.ClickException( u'Specify an api key either in {0}, on command line, ' u'or in API_KEY file.'.format(get_project_config_file_name()) ) if not username or not api_url or not apikey: raise click.ClickException(u'Missing username, api_url or api_key.') data = load_json_files(cfg) if files: data = [(fn, contents) for fn, contents in data if fn in files] # There are two modes: # # 1. User set category in configuration. Then the json files can # either have no category set or they have to have the same # category set. # # 2. User has NOT set category in configuration. Then the json # files must all have the category set. The categories can be # different. # # Go through and make sure there aren't any problems with # categories. all_categories = dict( [(cat['title'], cat) for cat in steve.richardapi.get_all_categories(api_url)]) try: category = cfg.get('project', 'category') category = category.strip() if category not in all_categories: raise click.ClickException( u'Category "{0}" does not exist on server. Build it there ' u'first.'.format(category) ) else: click.echo('Category {0} exists on site.'.format(category)) except NoOptionError: category = None errors = [] for fn, contents in data: if category is None: this_cat = contents.get('category') if not this_cat: errors.append( u'No category set in configuration and {0} has no ' u'category set.'.format(fn) ) elif this_cat != this_cat.strip(): errors.append( u'Category "{0}" has whitespace at beginning or ' u'end.'.format(this_cat) ) elif this_cat not in all_categories: errors.append( u'Category "{0}" does not exist on server. ' u'Build it there first.'.format(this_cat) ) else: this_cat = contents.get('category') if this_cat is not None and str(this_cat).strip() != category: errors.append( u'Category set in configuration ({0}), but {1} has ' u'different category ({2}).'.format(category, fn, this_cat) ) if update: for fn, contents in data: if 'id' not in contents: errors.append( u'id not in contents for "{0}".'.format(fn) ) if errors: raise click.ClickException('\n'.join(errors)) # Everything looks ok. So double-check with the user and push. click.echo('Pushing to: {0}'.format(api_url)) click.echo('Username: {0}'.format(username)) click.echo('api_key: {0}'.format(apikey)) click.echo('update?: {0}'.format(update)) click.echo('# videos: {0}'.format(len(data))) click.echo('Once you push, you can not undo it. Push for realz? Y/N') if not raw_input().strip().lower().startswith('y'): raise click.Abort() for fn, contents in data: contents['category'] = category or contents.get('category') if not update: # Nix any id field since that causes problems. if 'id' in contents: if not overwrite: click.echo(u'Skipping... already exists.') continue del contents['id'] click.echo('Pushing {0}'.format(fn)) try: vid = steve.richardapi.create_video(api_url, apikey, contents) if 'id' in vid: contents['id'] = vid['id'] click.echo(' Now has id {0}'.format(vid['id'])) else: click.echo(' Errors?: {0}'.format(vid), err=True) except steve.restapi.RestAPIException as exc: click.echo(' Error?: {0}'.format(exc), err=True) click.echo(' "{0}"'.format(exc.response.content), err=True) else: click.echo('Updating {0} "{1}" ({2})'.format( contents['id'], contents['title'], fn)) try: vid = steve.richardapi.update_video( api_url, apikey, contents['id'], contents) except steve.restapi.RestAPIException as exc: click.err(' Error?: {0}'.format(exc)) click.err(' "{0}"'.format(exc.response.content)) save_json_file(cfg, fn, contents)
def push_cmd(cfg, parser, parsed, args): if not parsed.quiet: parser.print_byline() # Get username, api_url and api_key. username = get_from_config(cfg, 'username') api_url = get_from_config(cfg, 'api_url') update = parsed.update # Command line api_key overrides config-set api_key api_key = parsed.apikey if not api_key: try: api_key = cfg.get('project', 'api_key') except ConfigParser.NoOptionError: pass if not api_key: err('Specify an api key either in steve.ini, on command line, ' 'or in API_KEY file.') return 1 if not username or not api_url or not api_key: return 1 data = load_json_files(cfg) if args: data = [(fn, contents) for fn, contents in data if fn in args] # There are two modes: # # 1. User set category in configuration. Then the json files can # either have no category set or they have to have the same # category set. # # 2. User has NOT set category in configuration. Then the json # files must all have the category set. The categories can be # different. # # Go through and make sure there aren't any problems with # categories. all_categories = dict([ (cat['title'], cat) for cat in steve.richardapi.get_all_categories(api_url) ]) try: category = cfg.get('project', 'category') category = category.strip() if category not in all_categories: err('Category "{0}" does not exist on server. Build it there ' 'first.'.format(category)) return 1 else: out('Category {0} exists on site.'.format(category)) except ConfigParser.NoOptionError: category = None errors = False for fn, contents in data: if category is None: this_cat = contents.get('category') if not this_cat: err('No category set in configuration and {0} has no ' 'category set.'.format(fn)) errors = True elif this_cat != this_cat.strip(): err('Category "{0}" has whitespace at beginning or ' 'end.'.format(this_cat)) return 1 elif this_cat not in all_categories: err('Category "{0}" does not exist on server. ' 'Build it there first.'.format(this_cat)) return 1 else: this_cat = contents.get('category') if this_cat is not None and str(this_cat).strip() != category: err('Category set in configuration ({0}), but {1} has ' 'different category ({2}).'.format(category, fn, this_cat)) errors = True if update: for fn, contents in data: if not 'id' in contents: err('id not in contents for "{0}".'.format(fn)) errors = True break if errors: err('Aborting.') return 1 # Everything looks ok. So double-check with the user and push. out('Pushing to: {0}'.format(api_url)) out('Username: {0}'.format(username)) out('api_key: {0}'.format(api_key)) out('update?: {0}'.format(update)) out('# videos: {0}'.format(len(data))) out('Once you push, you can not undo it. Push for realz? Y/N') if not raw_input().strip().lower().startswith('y'): err('Aborting.') return 1 for fn, contents in data: contents['category'] = category or contents.get('category') if not update: # Nix any id field since that causes problems. if 'id' in contents: if not parsed.overwrite: print 'Skipping... already exists' continue del contents['id'] out('Pushing {0}'.format(fn)) try: vid = steve.richardapi.create_video(api_url, api_key, contents) if 'id' in vid: contents['id'] = vid['id'] out(' Now has id {0}'.format(vid['id'])) else: err(' Errors?: {0}'.format(vid)) except steve.restapi.RestAPIException as exc: err(' Error?: {0}'.format(exc)) err(' "{0}"'.format(exc.response.content)) else: out('Updating {0} "{1}" ({2})'.format(contents['id'], contents['title'], fn)) try: vid = steve.richardapi.update_video(api_url, api_key, contents['id'], contents) except steve.restapi.RestAPIException as exc: err(' Error?: {0}'.format(exc)) err(' "{0}"'.format(exc.response.content)) save_json_file(cfg, fn, contents) return 0
def get_data(cfg, fn): data_files = load_json_files(cfg) data_files = [d for d in data_files if d[0][5:] == fn] return data_files[0] if data_files else None