open-enum-derive 0.5.2

An attribute for generating "open" C-like enums, those that accept any integer value, by using a newtype struct and associated constants
Documentation
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashSet;

use proc_macro2::{Ident, Span};
use quote::ToTokens;
use syn::{parse::Parse, Error, Token, Visibility};

pub struct Config {
    pub allow_alias: bool,
    pub repr_visibility: Visibility,
}

impl Parse for Config {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let mut out = Self {
            allow_alias: false,
            repr_visibility: Visibility::Public(Token![pub](Span::call_site())),
        };
        let mut seen_names = HashSet::new();
        while !input.is_empty() {
            let name: Ident = input.parse()?;
            let name_string = name.to_token_stream().to_string();
            let has_value = input.peek(Token![=]);
            if has_value {
                let _eq_token: Token![=] = input.parse()?;
            }
            match name_string.as_str() {
                "allow_alias" => {
                    if has_value {
                        let allow_alias: syn::LitBool = input.parse()?;
                        out.allow_alias = allow_alias.value;
                    } else {
                        out.allow_alias = true;
                    }
                }
                name_str @ "inner_vis" if !has_value => {
                    return Err(Error::new(
                        name.span(),
                        format!("Option `{name_str}` requires a value"),
                    ))
                }
                "inner_vis" => {
                    out.repr_visibility = input.parse()?;
                    if matches!(out.repr_visibility, syn::Visibility::Inherited) {
                        return Err(input.error("Expected visibility"));
                    }
                }
                unknown_name => {
                    return Err(Error::new(
                        name.span(),
                        format!("Unknown option `{unknown_name}`"),
                    ));
                }
            }
            if !input.is_empty() {
                let _comma: Token![,] = input.parse()?;
            }
            if !seen_names.insert(name_string) {
                return Err(Error::new(
                    name.span(),
                    format!(
                        "Option `{name}` listed more than once",
                        name = name.to_token_stream()
                    ),
                ));
            }
        }
        Ok(out)
    }
}