简体   繁体   English

如何按文件夹对文件(字符串)列表进行分组并对组执行 select 方法

[英]How to group list of files (strings) by folder and perform select method on the group

I'm creating a constant cs file for images in our project.我正在为我们项目中的图像创建一个常量 cs 文件。

So I have a list of files like so:所以我有一个文件列表,如下所示:

var SourceFiles = new List<String>() {
    "Images/BankLogos/ic_card_amex.svg", 
    "Images/BankLogos/ic_nocards.svg",
    "Images/Cars/Toyota_Auris_TS_Estate.png",
    "Images/Icons/ic_current_location_circle.svg",
    "Images/Icons/ic_abn_partially_available_circle.svg",
    "Images/ic_menu.svg",
};

I want to use Linq / Lambda.我想使用 Linq / Lambda。

I figure I must be able to group by slash and then within those group list perform some select methods to output each group section.我想我必须能够按斜线分组,然后在这些组列表中执行一些 select 方法到 output 每个组部分。

This is how the output should look.这就是 output 的外观。

// Generated code, do not edit.
namespace Common 
{
    public static class Constants
    {
        public static class Images
        {
            public static class BankLogos
            {
                public static readonly string IcCardAmex = "Images/BankLogos/ic_card_amex.svg";
                public static readonly string IcNocards = "Images/BankLogos/ic_nocards.svg";
            }

            public static class Cars
            {
                public static readonly string ImagesCarsToyotaAurisTsEstate = "Images/Cars/Toyota_Auris_TS_Estate.png";
            }

            public static class Icons
            {
                public static readonly string IcCurrentLocationCircle = "Images/Icons/ic_current_location_circle.svg";
                public static readonly string IcAbnPartiallyAvailableCircle = "Images/Icons/ic_abn_partially_available_circle.svg";
            }

            public static readonly string IcMenu = "Images/ic_menu.svg";
        }
    }
}

I have some code to write out the outer section..我有一些代码可以写出外部部分..

        private string GenerateCode(IEnumerable<string> files)
        {
            var content = string.Join(
                $"{Environment.NewLine}\t",
                files.Select(GenerateProperty));

            var code = $@"
// Generated code, do not edit.
namespace Common 
{{
    public static class Constants
    {{
        {content}
    }}
}}";
            return code;
        }

        private static string GenerateProperty(string file)
        {
            var ext = GetExt(file);
            var withoutExt = RemoveExt(ext, file);
            var name = LetterOrDigit(withoutExt);

            var v = file.Replace("\\", "\\\\");

            return string.Format(
                "public static readonly string {0} = \"{1}\";",
                name,
                v
            );
        }

So I figure if I can group the list used in the select and pass the list in the GenerateProperty I could write out each section like GenerateCode .所以我想如果我可以将 select 中使用的列表分组并在GenerateProperty中传递列表,我可以写出每个部分,如GenerateCode

So I think I can manage most of this, but I'm not sure who to group the files / string by the folder and then perform a select on that list?所以我认为我可以管理其中的大部分内容,但我不确定谁按文件夹对文件/字符串进行分组,然后在该列表上执行select

I use a lot of extension methods, so I built my answer using my existing library of extensions.我使用了很多扩展方法,因此我使用现有的扩展库构建了我的答案。

First, the extensions:首先,扩展:

public static class StringExt {
    // return new string consisting only of letter or digit characters from s
    public static string LetterOrDigit(this string s) => s.Where(c => Char.IsLetterOrDigit(c)).Join();

    // build new string from n copies of s
    public static string Repeat(this string s, int n) => new StringBuilder(s.Length * n).Insert(0, s, n).ToString();

    // return s upto (stopping before) the first occurrence of stopRE
    // or all of s if stopRE is not present
    public static string UpTo(this string s, Regex stopRE) {
        var m = stopRE.Match(s);
        return m.Success ? s.Substring(0, m.Index) : s;
    }

    // return s past the first occurrence of stopRE
    // or all of s if stopRE is not present
    public static string Past(this string s, Regex startRE) {
        var m = startRE.Match(s);
        return m.Success ? s.Substring(m.Index + m.Length) : s;
    }

    // return true if re is present in (matches) s
    public static bool IsMatch(this string s, Regex re) => re.IsMatch(s);
}

public static class IEnumerableExt {
    // return a new string by joining the chars together
    public static string Join(this IEnumerable<char> chars) => String.Concat(chars); // faster >= .Net Core 2.1
    // return a new string by combining the elements of s, separated by sep
    public static string Join(this IEnumerable<string> s, string sep) => String.Join(sep, s);
}

Next, a slight modification of your GenerateProperty method:接下来,稍微修改一下GenerateProperty方法:

private static string GenerateProperty(string file) {
    var ext = Path.GetExtension(file);
    var withoutExt = Path.GetFileNameWithoutExtension(file);
    var name = withoutExt.LetterOrDigit();

    var v = file.Replace("\\", "\\\\");
    return $"public static readonly string {name} = \"{v}\";";
}

Next, a new helper method that recursively generates the classes from the paths.接下来是一个新的辅助方法,它从路径递归地生成类。 The code is somewhat complicated by the fact that I used LINQ so I could use my Join method to insert newlines between the sections, so I wouldn't have extra blank lines.代码有点复杂,因为我使用了 LINQ,所以我可以使用我的Join方法在部分之间插入换行符,所以我不会有额外的空行。 In general, you can read enumerable.Select(v => as being the similar to foreach (var v in enumerable) .通常,您可以将enumerable.Select(v =>视为类似于foreach (var v in enumerable)

static Regex sepRE = new Regex(@"[/\\]", RegexOptions.Compiled);
private static string GenerateProperties(IEnumerable<string> files, int nestDepth = 1) {
    return Generator(files.Select(f => (full: f, left: f)), nestDepth);

    // internal nested method to convert list of paths to class declarations and member property declarations
    // calls self recursively to handle sub-folders in paths
    string Generator(IEnumerable<(string full, string left)> files, int nestDepth) {
        string tabs = "\t".Repeat(nestDepth + 2);

                  // group tuples of (full path, rest of path after first folder) by first path folder
        var ans = files.GroupBy(n => n.left.UpTo(sepRE), n => (n.full, left: n.left.Past(sepRE)))
                  // foreach (var fg in files.GroupBy...) -- fg is group of paths in a folder
                        // build class declaration from folder
                       .Select(fg => $"{tabs}public static class {fg.Key.LetterOrDigit()}\n{tabs}{{\n" +
                                    // group (separate) sub-paths into paths in further folders and paths in root of this folder/class
                                     fg.GroupBy(n => n.left.IsMatch(sepRE))
                                        // foreach (var sfg in GroupBy...) -- sfg is group of subfolder paths or root paths
                                       .Select(sfg =>
                                            sfg.Key
                                                // if in subfolders, call recursively to create classes for subfolders
                                                ? Generator(sfg, nestDepth + 1) // more nested classes
                                                // if in root, just create properties for each root member path
                                                : sfg.Select(n => tabs+"    "+GenerateProperty(n.full)).Join("\n")
                                       )
                                       // join the sub-folder declarations and root property declarations
                                       // separated by newlines
                                       .Join("\n") +
                                    // end the class declaration
                                     $"\n{tabs}}}\n")
                        // join the class declarations at a level with newlines
                        // (creating a blank line between each class declaration)
                       .Join("\n");

        return ans;
    }
}

Finally, a slightly modified version of your GenerateCode method to handle nesting and newlines properly, and call the GenerateProperties method:最后,稍微修改一下GenerateCode方法,以正确处理嵌套和换行符,并调用GenerateProperties方法:

private string GenerateCode(IEnumerable<string> files) {
    var content = string.Join(
        $"{Environment.NewLine}\t",
        GenerateProperties(files));

    var code = $@"
// Generated code, do not edit.
namespace Common
{{
    public static class Constants
    {{
{content}    }}
}}";
    return code;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM