Чтение параметров в автосоздаваемые переменные из ini файла одним сниппетом

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Реакции
824
Баллы
93
Решил поделиться полезняшкой. Она из разряда велосипедных велосипедов, но иногда весьма ускоряет работу.

Цель
Чтение из ini файла всех параметров с автоматическим созданием нужных переменных реализованное в виде одного сниппета без необходимости подключения библиотек, правок в общем коде или uses.


Область применения
Быстрая вставка сниппета в блок c# кода с целью ускорения разработки разноплановых мелких шабов, требующих чтения конфига.
Хранение в инишнике используется ввиду использования одного конфига энным кол-вом шаблонов, либо во избежание лишней работы с созданием входных настроек, либо для обеспечения мобильности настроек.

Как работает
Читает ini-шник лежащий рядом с шаблоном имя которого совпадает с именем шаблона. Либо задать конкретное имя файла в снипете.
Читает ini файл, если находит параметры - создает (если их нет) переменные с именем cfg_имясекции_имяпараметра и присваивает значение, если переменная есть - присваивает ей соответствующее значение. Если секции нет, то имя параметра будет cfg_имяпараметра.

Записи нет.. не было нужды, только чтение.

Ини файл:
upload_2017-10-2_15-20-9.png
Результат выполнения сниппета:
upload_2017-10-2_15-20-51.png

Благодарности

@Adigen - за методику создания переменных, @Lord_Alfred за наводку на методику =)

сам сниппет:
Код:
Развернуть Свернуть Копировать
//берем имя проекта и заменяем расширение на ini
string config_file = project.Name.Replace("xmlz","ini");
//если несколько проектов юзают один и тот же конфиг
//string config_file = "Config.ini";
string config_path = Path.Combine(project.Directory,config_file);

//проверяем существование конфига
if (File.Exists(config_path)){
    project.SendInfoToLog("Начинаем загрузку конфигурации из файла:"+config_file);  
      var vFile = File.ReadAllLines(config_path);
      var vList = new List<string>(vFile);
  
    string CurrentSection = String.Empty;
  
      foreach( string str in vList){              
        //чистим лишние пробелы и табуляции с начала и конца строки
        string cstr = str.Trim();
        //если пустышка - дальше
        if (cstr==String.Empty) continue;
        //если первый символ комментарий - дальше
        if (cstr[0]=='#'||cstr[0]==';') continue;
        //если строка соответствет секции сохраняем её имя
        Match m = Regex.Match(cstr,@"(?<=\[).*(?=])",RegexOptions.None);
        if (m.Success){
              CurrentSection = m.Value;
            project.SendInfoToLog("Нашли секцию " + CurrentSection);
            continue;
        }
      
        //ловим параметр и значение
        string ParamName = String.Empty;
        Match pn = Regex.Match(cstr,@".*?(?==)",RegexOptions.None);      
        Match pv = Regex.Match(cstr,@"(?<==).*",RegexOptions.None);      
      
        if (pn.Success&&pv.Success){
            project.SendInfoToLog("наши параметр "+pn.Value);            
          
            string vParamValue = pv.Value;
            string vParamName = String.Empty;
            if (CurrentSection==String.Empty){
                vParamName = "cfg_"+pn.Value;
            } else {
                vParamName = "cfg_"+CurrentSection+"_"+pn.Value;
            }
                //проверяем существование переменной, если нет то создаем новую
                if (project.Variables.Keys.Contains(vParamName)){
                    project.SendInfoToLog("Переменная "+vParamName+" уже существует - присваиваем ей значение");                  
                    project.Variables[vParamName].Value = vParamValue;
                } else {
                    project.SendInfoToLog("Создаем переменную "+vParamName+" и присваиваем ей значение");
                    object obj = project.Variables;
                    obj.GetType().GetMethod("QuickCreateVariable").Invoke(obj,new Object[]{vParamName});
                    project.Variables[vParamName].Value = vParamValue;
                }
        }
      }  
} else {
   project.SendInfoToLog("Отсутствует файл конфигурации: "+config_path);  
}
 
Последнее редактирование:
А можешь более подробно объяснить зачем?
что-то не допер
 
А можешь более подробно объяснить зачем?
что-то не допер
Предположим есть необходимость время от времени делать шабы читающие один и тот же конфиг.. шабы мелкие и разные, делать их можно быстро, но изрядно задалбывает создавать переменные, добавлять функционал чтения конфигурации. Пока это один конфиг - все еще более менее, т.к. можно подготовленный шаблон использовать, где это уже сделано. Но как только добавляется еще несколько конфигов - начинается пляска и путаница.
А так сниппет выполнил, он и переменные создал и данные в них прочитал. Поставил сниппет первым - он и дальнейшее чтение параметров в шабе обеспечит. Не нужно ручками ничего создавать и читать из файла.
Быстро наваял что нужно, используя уже созданные переменные, в зенку добавил и финита.
В общем небольшая автоматизация процесса создания автоматизации)

Ну а вторичный функционал - это банальное чтение параметров из инишника.. для случаев когда не используется бд и неохота заморачиваться с входными настройками.
 
  • Спасибо
Реакции: Nord, Sanekk и deopl
интересная задумка, единый конфиг для всех шабов в одной рабочей папке.
была похожая мысль давно еще, но потом вылетела из головы
 
Отличная инициатива и полезный сниппет! :-)
Надо будет его к какому-нибудь шаблону прикрутить и испытать в деле, а то у меня везде входящие настройки в виде конфигов, а это крайне неудобно (из ЗП копировать данные оттуда для отладки в ПМ - не веселое занятие, когда переменные по-умолчанию должны быть не production грубо говоря)
 
Читает ini-шник лежащий рядом с шаблоном имя которого совпадает с именем шаблона. Либо задать конкретное имя файла в снипете.
А как абсолютный путь прописать в сниппет?
Шаблоны в разных папках лежат, и до конфига получается дотянуться можно только через абсолютный путь.
 
string config_path = "путь";
В первую очередь я так и сделал. Но думал что может я что то не понимаю, и написал сюда.
Так не пашет.
Я так понимаю что на слеши ругается?
 

Вложения

  • QIP Shot - Screen 054.png
    QIP Shot - Screen 054.png
    34,1 KB · Просмотры: 69
  • QIP Shot - Screen 055.png
    QIP Shot - Screen 055.png
    14,8 KB · Просмотры: 66
"CS0103 Имя config_path отсутствует в текущем контексте Строка 8 Столбец 70"
Надо было раскомментировать это
string config_file = "Config.ini";

Так что все супер! Все добавляется!
Будем пользовать! :az:
 

Вложения

  • QIP Shot - Screen 056.png
    QIP Shot - Screen 056.png
    5,7 KB · Просмотры: 33
  • Спасибо
Реакции: DmitryAk
Когда я только познакомился с Зенкой сразу подумал - а нафиг нужны эти входящие настройки, надо же через ini. Но так и не сделал.
 
а нафиг нужны эти входящие настройки
Чистое имхо - входные хороши для интерактивной работы, когда выбрал параметры запуска, стартанул, посмотрел что в итоге, выбрал другие, снова запустил..
 
Работа с переменными - это хорошо, но не помешала бы ещё работа со списками/таблицами (получение имён и создание).
Ковырял метод Object.GetType(), но через него, по видимому, реализовать не получится - нет нужных методов и свойств.
 
К спискам и таблицам не добраться таким методом. ITables и ILists интерфейсы реализуют только get метод получения списка/таблицы по имени. Создание, список элементов, переименование и тд - где-то глубоко в недрах постера.
 
  • Спасибо
Реакции: Dimionix
хех )
а как правильно в файле INI запихать в переменную код для С# кубика? записывать код в строку?
и еще, как потом вставить в C# кубик эту переменную? я так понимаю что это не просто вставить переменную и все.

ps:
о я смотрю вопрос мой прям в продолжение разговора)
да, вот с таблицами бы еще как то снюхать, и все - вообще красота была бы!
 
Последнее редактирование:
Боюсь что это не получится. Если надо разделить код - его выносят в библиотеку и используют её.
Да, такой финт не проканал )
А как это "его выносят в библиотеку?"
 
Хорошая идея. Искал тут по своему вопросу, наткнулся на этот топик как наиболее релевантный. Но у меня всё же немного другой кейс.
Идея состоит в том чтобы где-то хранить настройки подключения к БД в зависимости от переменной-триггера pull_type(принимает одно из двух значений production|test). Хочется прийти к такому стандартному кубику:

T6unYsr.png



На сейчас приходится делать копии шабов из-за разных подключений к БД, что крайне геморройно. В общем, попробовал я это дело вынести в json-файл настроек и считывать его каждый раз в начале работы. Но вся загвоздка в том что в многопотоке нагрузка достаточно большая и через File.ReadAllText происходит параллельное обращение к файлу и следствие - креши. Может через привязанный список лучше будет, ну пока не пробовал... Может кто сталкивался с подобным вопросом уже и есть идеи получше?
 
Но вся загвоздка в том что в многопотоке нагрузка достаточно большая и через File.ReadAllText происходит параллельное обращение к файлу и следствие - креши
Очень странно. Я не спец в C#, но возможно данным методом файл может открываться и на запись (а нужно только чтение)?

Но вообще - стоит поискать ошибку в чем-то другом, вряд ли всё так плохо с данным методом. Скорее от парсинга json косяк может вылезти, чем оттуда, имхо.
 
Да, охрана отмена. Где-то накосячил с экспериментом. Всё ок.
 
Обновил сниппет из стартпоста, чтобы появилась возможность правильно парсить следующие строки в ini файле:
[Section]
variable_name=data[in='brakets']

Из изменений: поменял регулярку для парсинга секций + убрал регулярку для получения названий переменных и их значений и заменил это на indexOf + чуть отформатировал код.

C#:
Развернуть Свернуть Копировать
//берем имя проекта и заменяем расширение на ini
string config_file = project.Name.Replace("xmlz","ini");
//если несколько проектов юзают один и тот же конфиг
//string config_file = "config.ini";
string config_path = Path.Combine(project.Directory,config_file);
//проверяем существование конфига
if (File.Exists(config_path)) {
    project.SendInfoToLog("Начинаем загрузку конфигурации из файла:"+config_file);
    var vFile = File.ReadAllLines(config_path);
    var vList = new List<string>(vFile);

    string CurrentSection = String.Empty;
    foreach(string str in vList){
        //чистим лишние пробелы и табуляции с начала и конца строки
        string cstr = str.Trim();
        //если пустышка - дальше
        if (cstr==String.Empty) continue;
        //если первый символ комментарий - дальше
        if (cstr[0]=='#'||cstr[0]==';') continue;
        //если строка соответствет секции сохраняем её имя
        Match m = Regex.Match(cstr, @"(?<=^\[).+(?=]$)", RegexOptions.None);
        if (m.Success){
            CurrentSection = m.Value;
            project.SendInfoToLog("Нашли секцию " + CurrentSection);
            continue;
        }

        //ловим параметр и значение
        int pn_pos = cstr.IndexOf("="); // получаем первое равно, чтобы не поломать значение
        if (pn_pos == -1) continue; // хрен пойми что, лучше пропускать такое

        string pn = cstr.Substring(0, pn_pos);
        string pv = cstr.Substring(pn_pos+1, cstr.Length - pn_pos - 1);

        if (!String.IsNullOrEmpty(pn)&& !String.IsNullOrEmpty(pv)){
            project.SendInfoToLog("наши параметр "+pn);

            string vParamValue = pv;
            string vParamName = String.Empty;
            if (CurrentSection==String.Empty){
                vParamName = "cfg_"+pn;
            } else {
                vParamName = "cfg_"+CurrentSection+"_"+pn;
            }

            //проверяем существование переменной, если нет то создаем новую
            if (project.Variables.Keys.Contains(vParamName)){
                project.SendInfoToLog("Переменная "+vParamName+" уже существует - присваиваем ей значение");
                project.Variables[vParamName].Value = vParamValue;
            } else {
                project.SendInfoToLog("Создаем переменную "+vParamName+" и присваиваем ей значение");
                object obj = project.Variables;
                obj.GetType().GetMethod("QuickCreateVariable").Invoke(obj,new Object[]{vParamName});
                project.Variables[vParamName].Value = vParamValue;
            }
        }
    }
} else {
    project.SendInfoToLog("Отсутствует файл конфигурации: "+config_path);
}
 
Можно избавиться от регулярок и связанних проверок, если задействовать уже имеющиеся возможности для работы с ini-файлами.
Работа через WinAPI, но это лучше, чем регулярки.

Общий код:
Код:
Развернуть Свернуть Копировать
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Text.RegularExpressions;
using ZennoLab.CommandCenter;
using ZennoLab.InterfacesLibrary;
using ZennoLab.InterfacesLibrary.ProjectModel;
using ZennoLab.InterfacesLibrary.ProjectModel.Collections;
using ZennoLab.InterfacesLibrary.ProjectModel.Enums;
using ZennoLab.Macros;
using Global.ZennoExtensions;
using ZennoLab.Emulation;

namespace ZennoLab.OwnCode
{
    /// <summary>
    /// A simple class of the common code
    /// </summary>
    public class CommonCode
    {
        /// <summary>
        /// Lock this object to mark part of code for single thread execution
        /// </summary>
        public static object SyncObject = new object();

        // Insert your code here
    }

    public static class IniFileHelper
    {
        public static int capacity = 512;


        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        private static extern int GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder value, int size, string filePath);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        static extern int GetPrivateProfileString(string section, string key, string defaultValue, [In, Out] char[] value, int size, string filePath);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        private static extern int GetPrivateProfileSection(string section, IntPtr keyValue, int size, string filePath);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool WritePrivateProfileString(string section, string key, string value, string filePath);

        public static bool WriteValue(string section, string key, string value, string filePath)
        {
            return WritePrivateProfileString(section, key, value, filePath);
        }

        public static bool DeleteSection(string section, string filepath)
        {
            return WritePrivateProfileString(section, null, null, filepath);
        }

        public static bool DeleteKey(string section, string key, string filepath)
        {
            return WritePrivateProfileString(section, key, null, filepath);
        }

        public static string ReadValue(string section, string key, string filePath, string defaultValue = "")
        {
            var value = new StringBuilder(capacity);
            GetPrivateProfileString(section, key, defaultValue, value, value.Capacity, filePath);
            return value.ToString();
        }

        public static string[] ReadSections(string filePath)
        {
            // first line will not recognize if ini file is saved in UTF-8 with BOM
            while (true)
            {
                char[] chars = new char[capacity];
                int size = GetPrivateProfileString(null, null, "", chars, capacity, filePath);

                if (size == 0)
                {
                    return null;
                }

                if (size < capacity - 2)
                {
                    string result = new String(chars, 0, size);
                    string[] sections = result.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
                    return sections;
                }

                capacity = capacity * 2;
            }
        }

        public static string[] ReadKeys(string section, string filePath)
        {
            // first line will not recognize if ini file is saved in UTF-8 with BOM
            while (true)
            {
                char[] chars = new char[capacity];
                int size = GetPrivateProfileString(section, null, "", chars, capacity, filePath);

                if (size == 0)
                {
                    return null;
                }

                if (size < capacity - 2)
                {
                    string result = new String(chars, 0, size);
                    string[] keys = result.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
                    return keys;
                }

                capacity = capacity * 2;
            }
        }

        public static string[] ReadKeyValuePairs(string section, string filePath)
        {
            while (true)
            {
                IntPtr returnedString = Marshal.AllocCoTaskMem(capacity * sizeof(char));
                int size = GetPrivateProfileSection(section, returnedString, capacity, filePath);

                if (size == 0)
                {
                    Marshal.FreeCoTaskMem(returnedString);
                    return null;
                }

                if (size < capacity - 2)
                {
                    string result = Marshal.PtrToStringAuto(returnedString, size - 1);
                    Marshal.FreeCoTaskMem(returnedString);
                    string[] keyValuePairs = result.Split('\0');
                    return keyValuePairs;
                }

                Marshal.FreeCoTaskMem(returnedString);
                capacity = capacity * 2;
            }
        }
    }
}
Код с MSDN'а.

Сниппет:
Код:
Развернуть Свернуть Копировать
//берем папку + имя проекта и заменяем расширение на ini
string config_file = project.Path + project.Name.Replace("xmlz","ini");
//если несколько проектов юзают один и тот же конфиг
//string config_file = project.Path + "config.ini";
string config_path = Path.Combine(project.Directory,config_file);
//проверяем отсутствие конфига
if (!File.Exists(config_path)) {
    project.SendWarningToLog("Отсутствует файл конфигурации: "+config_path);
    return "";
}
project.SendInfoToLog("Начинаем загрузку конфигурации из файла:"+config_file);
string[] sections = IniFileHelper.ReadSections(config_file);
foreach(string sct in sections)
{
    project.SendInfoToLog("Секция: "+sct);
    string[] keys = IniFileHelper.ReadKeys(sct, config_file);
    foreach(string key in keys)
    {
        string value = IniFileHelper.ReadValue(sct, key, config_file);
 
        if (!String.IsNullOrEmpty(key)&& !String.IsNullOrEmpty(value)){
            project.SendInfoToLog("\tКлюч: "+key);

            string vParamValue = value;
            string vParamName = String.Empty;
            if (sct == String.Empty){
                vParamName = "cfg_"+key;
            } else {
                vParamName = "cfg_"+sct+"_"+key;
            }

            //проверяем существование переменной, если нет то создаем новую
            if (project.Variables.Keys.Contains(vParamName)){
                project.SendInfoToLog("Переменная "+vParamName+" уже существует - присваиваем ей значение: "+value);
                project.Variables[vParamName].Value = vParamValue;
            } else {
                project.SendInfoToLog("Создаем переменную "+vParamName+" и присваиваем ей значение: "+value);
                object obj = project.Variables;
                obj.GetType().GetMethod("QuickCreateVariable").Invoke(obj,new Object[]{vParamName});
                project.Variables[vParamName].Value = vParamValue;
            }
        }
    }
}
 

Вложения

Последнее редактирование:
Можно избавиться от регулярок и связанних проверок, если задействовать уже имеющиеся возможности для работы с ini-файлами.
Работа через WinAPI, но это лучше, чем регулярки.

Общий код:

Цель
Чтение из ini файла всех параметров с автоматическим созданием нужных переменных реализованное в виде одного сниппета без необходимости подключения библиотек, правок в общем коде или uses.
Вся цель сниппета в быстром подключении. Т.е. буквально бросили кубик, впихнули сниппет и пошли дальше. А если полноценно работать с инишником, то да, лучше через винапи.
 
в виде одного сниппета без необходимости подключения библиотек, правок в общем коде или uses.
Этот момент, я упустил :bk:
Вся цель сниппета в быстром подключении. Т.е. буквально бросили кубик, впихнули сниппет и пошли дальше. А если полноценно работать с инишником, то да, лучше через винапи.
Вообще можно и это впихнуть в метод, но в данном случае, для меня пока сложно, да и выглядеть будет тонной кода.
 
Последнее редактирование:
Что то у меня месиво какое то создалось. Видимо в кодировке проблема но не уверен. Копировал код из 1го сообщения. Кодировка инишки UTF8 без BOM.
Скрин: https://s.mail.ru/9Zov/NmyWBZEAt
Проект во вложении.

Вся цель сниппета в быстром подключении. Т.е. буквально бросили кубик, впихнули сниппет и пошли дальше. А если полноценно работать с инишником, то да, лучше через винапи.
 

Вложения

Работа с переменными - это хорошо, но не помешала бы ещё работа со списками/таблицами (получение имён и создание).
Ковырял метод Object.GetType(), но через него, по видимому, реализовать не получится - нет нужных методов и свойств.
Здравствуйте. Вы не нашли решение по Object.GetType() создание/ удаление переменных и списков?
PS Вопрос закрыт
 
Последнее редактирование:
Распарсить json разве не делает тоже самое? Он и переменные создает.
 
  • Спасибо
Реакции: bizzon

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)