prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  info: Select specific fields
  Right now beet info spews all tags, but it should have an option for pulling a single key/value, like so:
  
  > $ beet info -k acoustid_id "05 Le Freak.aiff"
  > 616e1881-cb7e-4d9f-9988-fa2b546c7085
  
  This is useful if you wanted to do a multi-step operation cleanly from the command line.
  
  Here is the bash script I am using to get around this right now:
  
  > # !/usr/bin/env bash
  > # 
  > 
  > FILE=$1
  > 
  > if [ -z "$FILE" ]; then
  >   echo "need filename"
  >   exit 0
  > fi
  > 
  > beet info "$FILE" | grep acoustid_id | sed -e 's/^ *acoustid_id: //g' 
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `beetsplug/info.py`
  Content:
  ```
  1 # This file is part of beets.
  2 # Copyright 2015, Adrian Sampson.
  3 #
  4 # Permission is hereby granted, free of charge, to any person obtaining
  5 # a copy of this software and associated documentation files (the
  6 # "Software"), to deal in the Software without restriction, including
  7 # without limitation the rights to use, copy, modify, merge, publish,
  8 # distribute, sublicense, and/or sell copies of the Software, and to
  9 # permit persons to whom the Software is furnished to do so, subject to
  10 # the following conditions:
  11 #
  12 # The above copyright notice and this permission notice shall be
  13 # included in all copies or substantial portions of the Software.
  14 
  15 """Shows file metadata.
  16 """
  17 
  18 from __future__ import (division, absolute_import, print_function,
  19                         unicode_literals)
  20 
  21 import os
  22 
  23 from beets.plugins import BeetsPlugin
  24 from beets import ui
  25 from beets import mediafile
  26 from beets.util import displayable_path, normpath, syspath
  27 
  28 
  29 def tag_data(lib, args):
  30     query = []
  31     for arg in args:
  32         path = normpath(arg)
  33         if os.path.isfile(syspath(path)):
  34             yield tag_data_emitter(path)
  35         else:
  36             query.append(arg)
  37 
  38     if query:
  39         for item in lib.items(query):
  40             yield tag_data_emitter(item.path)
  41 
  42 
  43 def tag_data_emitter(path):
  44     def emitter():
  45         fields = list(mediafile.MediaFile.readable_fields())
  46         fields.remove('images')
  47         mf = mediafile.MediaFile(syspath(path))
  48         tags = {}
  49         for field in fields:
  50             tags[field] = getattr(mf, field)
  51         tags['art'] = mf.art is not None
  52         tags['path'] = displayable_path(path)
  53         return tags
  54     return emitter
  55 
  56 
  57 def library_data(lib, args):
  58     for item in lib.items(args):
  59         yield library_data_emitter(item)
  60 
  61 
  62 def library_data_emitter(item):
  63     def emitter():
  64         data = dict(item.formatted())
  65         data['path'] = displayable_path(item.path)
  66         return data
  67     return emitter
  68 
  69 
  70 def update_summary(summary, tags):
  71     for key, value in tags.iteritems():
  72         if key not in summary:
  73             summary[key] = value
  74         elif summary[key] != value:
  75             summary[key] = '[various]'
  76     return summary
  77 
  78 
  79 def print_data(data):
  80     path = data.pop('path')
  81     formatted = {}
  82     for key, value in data.iteritems():
  83         if isinstance(value, list):
  84             formatted[key] = u'; '.join(value)
  85         if value is not None:
  86             formatted[key] = value
  87 
  88     maxwidth = max(len(key) for key in formatted)
  89     lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth)
  90 
  91     if path:
  92         ui.print_(displayable_path(path))
  93 
  94     for field in sorted(formatted):
  95         value = formatted[field]
  96         if isinstance(value, list):
  97             value = u'; '.join(value)
  98         ui.print_(lineformat.format(field, value))
  99 
  100 
  101 class InfoPlugin(BeetsPlugin):
  102 
  103     def commands(self):
  104         cmd = ui.Subcommand('info', help='show file metadata')
  105         cmd.func = self.run
  106         cmd.parser.add_option('-l', '--library', action='store_true',
  107                               help='show library fields instead of tags')
  108         cmd.parser.add_option('-s', '--summarize', action='store_true',
  109                               help='summarize the tags of all files')
  110         return [cmd]
  111 
  112     def run(self, lib, opts, args):
  113         """Print tag info or library data for each file referenced by args.
  114 
  115         Main entry point for the `beet info ARGS...` command.
  116 
  117         If an argument is a path pointing to an existing file, then the tags
  118         of that file are printed. All other arguments are considered
  119         queries, and for each item matching all those queries the tags from
  120         the file are printed.
  121 
  122         If `opts.summarize` is true, the function merges all tags into one
  123         dictionary and only prints that. If two files have different values
  124         for the same tag, the value is set to '[various]'
  125         """
  126         if opts.library:
  127             data_collector = library_data
  128         else:
  129             data_collector = tag_data
  130 
  131         first = True
  132         summary = {}
  133         for data_emitter in data_collector(lib, ui.decargs(args)):
  134             try:
  135                 data = data_emitter()
  136             except mediafile.UnreadableFileError as ex:
  137                 self._log.error(u'cannot read file: {0}', ex)
  138                 continue
  139 
  140             if opts.summarize:
  141                 update_summary(summary, data)
  142             else:
  143                 if not first:
  144                     ui.print_()
  145                 print_data(data)
  146                 first = False
  147 
  148         if opts.summarize:
  149             print_data(summary)
  150
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/beetsplug/info.py b/beetsplug/info.py
  --- a/beetsplug/info.py
  +++ b/beetsplug/info.py
  @@ -19,6 +19,7 @@
                           unicode_literals)
   
   import os
  +import re
   
   from beets.plugins import BeetsPlugin
   from beets import ui
  @@ -77,7 +78,7 @@
   
   
   def print_data(data):
  -    path = data.pop('path')
  +    path = data.pop('path', None)
       formatted = {}
       for key, value in data.iteritems():
           if isinstance(value, list):
  @@ -85,6 +86,9 @@
           if value is not None:
               formatted[key] = value
   
  +    if len(formatted) == 0:
  +        return
  +
       maxwidth = max(len(key) for key in formatted)
       lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth)
   
  @@ -107,6 +111,9 @@
                                 help='show library fields instead of tags')
           cmd.parser.add_option('-s', '--summarize', action='store_true',
                                 help='summarize the tags of all files')
  +        cmd.parser.add_option('-i', '--include-keys', default=[],
  +                              action='append', dest='included_keys',
  +                              help='comma separated list of keys to show')
           return [cmd]
   
       def run(self, lib, opts, args):
  @@ -128,6 +135,11 @@
           else:
               data_collector = tag_data
   
  +        included_keys = []
  +        for keys in opts.included_keys:
  +            included_keys.extend(keys.split(','))
  +        key_filter = make_key_filter(included_keys)
  +
           first = True
           summary = {}
           for data_emitter in data_collector(lib, ui.decargs(args)):
  @@ -137,6 +149,9 @@
                   self._log.error(u'cannot read file: {0}', ex)
                   continue
   
  +            path = data.get('path')
  +            data = key_filter(data)
  +            data['path'] = path  # always show path
               if opts.summarize:
                   update_summary(summary, data)
               else:
  @@ -147,3 +162,33 @@
   
           if opts.summarize:
               print_data(summary)
  +
  +
  +def make_key_filter(include):
  +    """Return a function that filters a dictionary.
  +
  +    The returned filter takes a dictionary and returns another
  +    dictionary that only includes the key-value pairs where the key
  +    glob-matches one of the keys in `include`.
  +    """
  +    if not include:
  +        return identity
  +
  +    matchers = []
  +    for key in include:
  +        key = re.escape(key)
  +        key = key.replace(r'\*', '.*')
  +        matchers.append(re.compile(key + '$'))
  +
  +    def filter(data):
  +        filtered = dict()
  +        for key, value in data.items():
  +            if any(map(lambda m: m.match(key), matchers)):
  +                filtered[key] = value
  +        return filtered
  +
  +    return filter
  +
  +
  +def identity(val):
  +    return val
  
verification_info: {"golden_diff": "diff --git a/beetsplug/info.py b/beetsplug/info.py\n--- a/beetsplug/info.py\n+++ b/beetsplug/info.py\n@@ -19,6 +19,7 @@\n                         unicode_literals)\n \n import os\n+import re\n \n from beets.plugins import BeetsPlugin\n from beets import ui\n@@ -77,7 +78,7 @@\n \n \n def print_data(data):\n-    path = data.pop('path')\n+    path = data.pop('path', None)\n     formatted = {}\n     for key, value in data.iteritems():\n         if isinstance(value, list):\n@@ -85,6 +86,9 @@\n         if value is not None:\n             formatted[key] = value\n \n+    if len(formatted) == 0:\n+        return\n+\n     maxwidth = max(len(key) for key in formatted)\n     lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth)\n \n@@ -107,6 +111,9 @@\n                               help='show library fields instead of tags')\n         cmd.parser.add_option('-s', '--summarize', action='store_true',\n                               help='summarize the tags of all files')\n+        cmd.parser.add_option('-i', '--include-keys', default=[],\n+                              action='append', dest='included_keys',\n+                              help='comma separated list of keys to show')\n         return [cmd]\n \n     def run(self, lib, opts, args):\n@@ -128,6 +135,11 @@\n         else:\n             data_collector = tag_data\n \n+        included_keys = []\n+        for keys in opts.included_keys:\n+            included_keys.extend(keys.split(','))\n+        key_filter = make_key_filter(included_keys)\n+\n         first = True\n         summary = {}\n         for data_emitter in data_collector(lib, ui.decargs(args)):\n@@ -137,6 +149,9 @@\n                 self._log.error(u'cannot read file: {0}', ex)\n                 continue\n \n+            path = data.get('path')\n+            data = key_filter(data)\n+            data['path'] = path  # always show path\n             if opts.summarize:\n                 update_summary(summary, data)\n             else:\n@@ -147,3 +162,33 @@\n \n         if opts.summarize:\n             print_data(summary)\n+\n+\n+def make_key_filter(include):\n+    \"\"\"Return a function that filters a dictionary.\n+\n+    The returned filter takes a dictionary and returns another\n+    dictionary that only includes the key-value pairs where the key\n+    glob-matches one of the keys in `include`.\n+    \"\"\"\n+    if not include:\n+        return identity\n+\n+    matchers = []\n+    for key in include:\n+        key = re.escape(key)\n+        key = key.replace(r'\\*', '.*')\n+        matchers.append(re.compile(key + '$'))\n+\n+    def filter(data):\n+        filtered = dict()\n+        for key, value in data.items():\n+            if any(map(lambda m: m.match(key), matchers)):\n+                filtered[key] = value\n+        return filtered\n+\n+    return filter\n+\n+\n+def identity(val):\n+    return val\n", "issue": "info: Select specific fields\nRight now beet info spews all tags, but it should have an option for pulling a single key/value, like so:\n\n> $ beet info -k acoustid_id \"05 Le Freak.aiff\"\n> 616e1881-cb7e-4d9f-9988-fa2b546c7085\n\nThis is useful if you wanted to do a multi-step operation cleanly from the command line.\n\nHere is the bash script I am using to get around this right now:\n\n> # !/usr/bin/env bash\n> # \n> \n> FILE=$1\n> \n> if [ -z \"$FILE\" ]; then\n>   echo \"need filename\"\n>   exit 0\n> fi\n> \n> beet info \"$FILE\" | grep acoustid_id | sed -e 's/^ *acoustid_id: //g' \n\n", "before_files": [{"content": "# This file is part of beets.\n# Copyright 2015, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Shows file metadata.\n\"\"\"\n\nfrom __future__ import (division, absolute_import, print_function,\n                        unicode_literals)\n\nimport os\n\nfrom beets.plugins import BeetsPlugin\nfrom beets import ui\nfrom beets import mediafile\nfrom beets.util import displayable_path, normpath, syspath\n\n\ndef tag_data(lib, args):\n    query = []\n    for arg in args:\n        path = normpath(arg)\n        if os.path.isfile(syspath(path)):\n            yield tag_data_emitter(path)\n        else:\n            query.append(arg)\n\n    if query:\n        for item in lib.items(query):\n            yield tag_data_emitter(item.path)\n\n\ndef tag_data_emitter(path):\n    def emitter():\n        fields = list(mediafile.MediaFile.readable_fields())\n        fields.remove('images')\n        mf = mediafile.MediaFile(syspath(path))\n        tags = {}\n        for field in fields:\n            tags[field] = getattr(mf, field)\n        tags['art'] = mf.art is not None\n        tags['path'] = displayable_path(path)\n        return tags\n    return emitter\n\n\ndef library_data(lib, args):\n    for item in lib.items(args):\n        yield library_data_emitter(item)\n\n\ndef library_data_emitter(item):\n    def emitter():\n        data = dict(item.formatted())\n        data['path'] = displayable_path(item.path)\n        return data\n    return emitter\n\n\ndef update_summary(summary, tags):\n    for key, value in tags.iteritems():\n        if key not in summary:\n            summary[key] = value\n        elif summary[key] != value:\n            summary[key] = '[various]'\n    return summary\n\n\ndef print_data(data):\n    path = data.pop('path')\n    formatted = {}\n    for key, value in data.iteritems():\n        if isinstance(value, list):\n            formatted[key] = u'; '.join(value)\n        if value is not None:\n            formatted[key] = value\n\n    maxwidth = max(len(key) for key in formatted)\n    lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth)\n\n    if path:\n        ui.print_(displayable_path(path))\n\n    for field in sorted(formatted):\n        value = formatted[field]\n        if isinstance(value, list):\n            value = u'; '.join(value)\n        ui.print_(lineformat.format(field, value))\n\n\nclass InfoPlugin(BeetsPlugin):\n\n    def commands(self):\n        cmd = ui.Subcommand('info', help='show file metadata')\n        cmd.func = self.run\n        cmd.parser.add_option('-l', '--library', action='store_true',\n                              help='show library fields instead of tags')\n        cmd.parser.add_option('-s', '--summarize', action='store_true',\n                              help='summarize the tags of all files')\n        return [cmd]\n\n    def run(self, lib, opts, args):\n        \"\"\"Print tag info or library data for each file referenced by args.\n\n        Main entry point for the `beet info ARGS...` command.\n\n        If an argument is a path pointing to an existing file, then the tags\n        of that file are printed. All other arguments are considered\n        queries, and for each item matching all those queries the tags from\n        the file are printed.\n\n        If `opts.summarize` is true, the function merges all tags into one\n        dictionary and only prints that. If two files have different values\n        for the same tag, the value is set to '[various]'\n        \"\"\"\n        if opts.library:\n            data_collector = library_data\n        else:\n            data_collector = tag_data\n\n        first = True\n        summary = {}\n        for data_emitter in data_collector(lib, ui.decargs(args)):\n            try:\n                data = data_emitter()\n            except mediafile.UnreadableFileError as ex:\n                self._log.error(u'cannot read file: {0}', ex)\n                continue\n\n            if opts.summarize:\n                update_summary(summary, data)\n            else:\n                if not first:\n                    ui.print_()\n                print_data(data)\n                first = False\n\n        if opts.summarize:\n            print_data(summary)\n", "path": "beetsplug/info.py"}], "after_files": [{"content": "# This file is part of beets.\n# Copyright 2015, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Shows file metadata.\n\"\"\"\n\nfrom __future__ import (division, absolute_import, print_function,\n                        unicode_literals)\n\nimport os\nimport re\n\nfrom beets.plugins import BeetsPlugin\nfrom beets import ui\nfrom beets import mediafile\nfrom beets.util import displayable_path, normpath, syspath\n\n\ndef tag_data(lib, args):\n    query = []\n    for arg in args:\n        path = normpath(arg)\n        if os.path.isfile(syspath(path)):\n            yield tag_data_emitter(path)\n        else:\n            query.append(arg)\n\n    if query:\n        for item in lib.items(query):\n            yield tag_data_emitter(item.path)\n\n\ndef tag_data_emitter(path):\n    def emitter():\n        fields = list(mediafile.MediaFile.readable_fields())\n        fields.remove('images')\n        mf = mediafile.MediaFile(syspath(path))\n        tags = {}\n        for field in fields:\n            tags[field] = getattr(mf, field)\n        tags['art'] = mf.art is not None\n        tags['path'] = displayable_path(path)\n        return tags\n    return emitter\n\n\ndef library_data(lib, args):\n    for item in lib.items(args):\n        yield library_data_emitter(item)\n\n\ndef library_data_emitter(item):\n    def emitter():\n        data = dict(item.formatted())\n        data['path'] = displayable_path(item.path)\n        return data\n    return emitter\n\n\ndef update_summary(summary, tags):\n    for key, value in tags.iteritems():\n        if key not in summary:\n            summary[key] = value\n        elif summary[key] != value:\n            summary[key] = '[various]'\n    return summary\n\n\ndef print_data(data):\n    path = data.pop('path', None)\n    formatted = {}\n    for key, value in data.iteritems():\n        if isinstance(value, list):\n            formatted[key] = u'; '.join(value)\n        if value is not None:\n            formatted[key] = value\n\n    if len(formatted) == 0:\n        return\n\n    maxwidth = max(len(key) for key in formatted)\n    lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth)\n\n    if path:\n        ui.print_(displayable_path(path))\n\n    for field in sorted(formatted):\n        value = formatted[field]\n        if isinstance(value, list):\n            value = u'; '.join(value)\n        ui.print_(lineformat.format(field, value))\n\n\nclass InfoPlugin(BeetsPlugin):\n\n    def commands(self):\n        cmd = ui.Subcommand('info', help='show file metadata')\n        cmd.func = self.run\n        cmd.parser.add_option('-l', '--library', action='store_true',\n                              help='show library fields instead of tags')\n        cmd.parser.add_option('-s', '--summarize', action='store_true',\n                              help='summarize the tags of all files')\n        cmd.parser.add_option('-i', '--include-keys', default=[],\n                              action='append', dest='included_keys',\n                              help='comma separated list of keys to show')\n        return [cmd]\n\n    def run(self, lib, opts, args):\n        \"\"\"Print tag info or library data for each file referenced by args.\n\n        Main entry point for the `beet info ARGS...` command.\n\n        If an argument is a path pointing to an existing file, then the tags\n        of that file are printed. All other arguments are considered\n        queries, and for each item matching all those queries the tags from\n        the file are printed.\n\n        If `opts.summarize` is true, the function merges all tags into one\n        dictionary and only prints that. If two files have different values\n        for the same tag, the value is set to '[various]'\n        \"\"\"\n        if opts.library:\n            data_collector = library_data\n        else:\n            data_collector = tag_data\n\n        included_keys = []\n        for keys in opts.included_keys:\n            included_keys.extend(keys.split(','))\n        key_filter = make_key_filter(included_keys)\n\n        first = True\n        summary = {}\n        for data_emitter in data_collector(lib, ui.decargs(args)):\n            try:\n                data = data_emitter()\n            except mediafile.UnreadableFileError as ex:\n                self._log.error(u'cannot read file: {0}', ex)\n                continue\n\n            path = data.get('path')\n            data = key_filter(data)\n            data['path'] = path  # always show path\n            if opts.summarize:\n                update_summary(summary, data)\n            else:\n                if not first:\n                    ui.print_()\n                print_data(data)\n                first = False\n\n        if opts.summarize:\n            print_data(summary)\n\n\ndef make_key_filter(include):\n    \"\"\"Return a function that filters a dictionary.\n\n    The returned filter takes a dictionary and returns another\n    dictionary that only includes the key-value pairs where the key\n    glob-matches one of the keys in `include`.\n    \"\"\"\n    if not include:\n        return identity\n\n    matchers = []\n    for key in include:\n        key = re.escape(key)\n        key = key.replace(r'\\*', '.*')\n        matchers.append(re.compile(key + '$'))\n\n    def filter(data):\n        filtered = dict()\n        for key, value in data.items():\n            if any(map(lambda m: m.match(key), matchers)):\n                filtered[key] = value\n        return filtered\n\n    return filter\n\n\ndef identity(val):\n    return val\n", "path": "beetsplug/info.py"}]}