Repository: beetbox/beets

Issue:
`import --set` doesn't work if the value contains a = character
### Problem

I’m attempting to set a field on the CLI during import to a URL, and I’m getting 'supplied argument is not of the form key=value'. I’ve tried escaping the string, I’ve tried single and double quotes, all without luck.

Here’s an example you can use (it errors out during argument processing, so it doesn’t even check for the existence of the directory):

```sh
$ beet import --set import_dir='Example Artist - Example Album (2018)' --set permalink='https://example.com/releases.php?release_id=01239840' Example\ Artist\ \-\ Example\ Album\ \(2018\)
error: supplied argument `permalink=https://example.com/releases.php?release_id=01239840' is not of the form `key=value'
```

Removing the = from the URL allows the import to proceed, but with an incorrect URL. Discussion of this issue can be found here: https://discourse.beets.io/t/set-field-to-url/

### Setup

* OS: Linux
* Python version: 2.7.9
* beets version: 1.4.6
* Turning off plugins made problem go away (yes/no): N/A

My configuration (output of `beet config`) is:

```yaml
library: /home/REDACTED/.config/beets/flac.blb
asciify_paths: yes
fetchart:
    sources: coverart itunes amazon albumart wikipedia google filesystem
    google_key: REDACTED
    store_source: yes
    auto: yes
    minwidth: 0
    google_engine: REDACTED MANUALLY
    enforce_ratio: no
    cautious: no
    maxwidth: 0
    fanarttv_key: REDACTED
    cover_names:
    - cover
    - front
    - art
    - album
    - folder
original_date: yes
replaygain:
    backend: gstreamer
    overwrite: yes
    targetlevel: 89
    auto: yes
    r128: [Opus]
copyartifacts:
    print_ignored: no
    extensions: .*
pluginpath: ~/.config/beets/plugins

plugins:
- chroma
- discogs
- acousticbrainz
- embedart
- fetchart
- ftintitle
- importadded
- lastgenre
- mbsync
- replaygain
- bucket
- inline
- the
- badfiles
- duplicates
- missing
- info
- bandcamp
- rymgenre
- copyartifacts

paths:
    default: $format/Albums/%bucket{%upper{%left{%the{$albumartist},1}}}/%the{$albumartist}/[%if{$original_year,$original_year,0000}] $album %aunique{albumartist album year, albumtype label catalognum albumdisambig}/%if{$multidisc,$disc-}$track - $title
    singleton: $format/Singles/%%the{$artist} - $title
    comp: $format/Compilations/[%if{$original_year,$original_year,0000}] %the{$album%if{%aunique, %aunique{albumartist album year, albumtype label catalognum albumdisambig}}}/%if{$multidisc,$disc-}$track - $title
    albumtype:soundtrack: $format/Soundtracks/[%if{$original_year,$original_year,0000}] %the{$album%if{%aunique, %aunique{albumartist album year, albumtype label catalognum albumdisambig}}}/%if{$multidisc,$disk-}$track - $title
    ext:cue: $albumpath/$album
    ext:CUE: $albumpath/$album
    ext:log: $albumpath/$album
    ext:LOG: $albumpath/$album
    ext:m3u: $albumpath/$album
    ext:m3u8: $albumpath/$album
    ext:pls: $albumpath/$album
    ext:nfo: $albumpath/$album
    ext:sfv: $albumpath/$album
    ext:ffp: $albumpath/$album
    ext:accurip: $albumpath/$album
threaded: yes
item_fields:
    multidisc: 1 if disctotal > 1 else 0
per_disc_numbering: yes
bucket:
    bucket_alpha:
    - _
    - A
    - B
    - C
    - D
    - E
    - F
    - G
    - H
    - I
    - J
    - K
    - L
    - M
    - N
    - O
    - P
    - Q
    - R
    - S
    - T
    - U
    - V
    - W
    - X
    - Y
    - Z
    bucket_alpha_regex:
        _: ^[^A-Z]
    bucket_year: []
    extrapolate: no
ignore: .AppleDouble ._* *~ .DS_Store
lastgenre:
    canonical: ~/.config/beets/genres-tree.yaml
    whitelist: ~/.config/beets/genres.txt
    min_weight: 20
    source: artist
    fallback: Unknown
    count: 1
    prefer_specific: no
    force: yes
    auto: yes
    separator: ', '
rymgenre:
    classes: primary
    depth: node
    separator: ', '
directory: /mnt/music/

import:
    detail: yes
    resume: ask
    incremental: no
    timid: no
    duplicate_action: skip
    quiet_fallback: skip
    none_rec_fallback: ask
    languages: en
    log: ~/.config/beets/import.log
echonest:
    apikey: REDACTED MANUALLY
    auto: yes
acoustid:
    apikey: REDACTED
the:
    a: yes
    patterns: []
    the: yes
    strip: no
    format: '{0}, {1}'
discogs:
    tokenfile: discogs_token.json
    user_token: REDACTED
    apikey: REDACTED
    apisecret: REDACTED
    source_weight: 0.5
bandcamp:
    min_candidates: 5
    art: no
    lyrics: no
    source_weight: 0.5
pathfields: {}
album_fields: {}
importadded:
    preserve_write_mtimes: no
    preserve_mtimes: no
duplicates:
    count: no
    full: no
    format: ''
    keys: []
    move: ''
    tag: ''
    path: no
    copy: ''
    tiebreak: {}
    album: no
    strict: no
    checksum: ''
    merge: no
    delete: no
chroma:
    auto: yes
acousticbrainz:
    auto: yes
    force: no
    tags: []
missing:
    count: no
    album: no
    total: no
embedart:
    compare_threshold: 0
    auto: yes
    ifempty: no
    remove_art_file: no
    maxwidth: 0
ftintitle:
    auto: yes
    drop: no
    format: feat. {0}
```



Discussion:
Thanks for reporting! The right fix will be to make the splitting in `_store_dict`:
https://github.com/beetbox/beets/blob/4f156b99bc821d6f342b154cffe79ce0336403c2/beets/ui/__init__.py#L790

More flexible, the same way that `modify_parse_args` works:
https://github.com/beetbox/beets/blob/4f156b99bc821d6f342b154cffe79ce0336403c2/beets/ui/commands.py#L1438

In fact, I'm not really sure why this is implemented twice. We should be reusing the same code for both cases.

Resolving patch:
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -799,8 +799,11 @@ def _store_dict(option, opt_str, value, parser):
         setattr(parser.values, dest, {})
         option_values = getattr(parser.values, dest)
 
+    # Decode the argument using the platform's argument encoding.
+    value = util.text_string(value, util.arg_encoding())
+
     try:
-        key, value = map(lambda s: util.text_string(s), value.split('='))
+        key, value = value.split('=', 1)
         if not (key and value):
             raise ValueError
     except ValueError:


Test patch: