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

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
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);  
}
 
Последнее редактирование:

deopl

Client
Регистрация
06.12.2011
Сообщения
656
Благодарностей
125
Баллы
43
А можешь более подробно объяснить зачем?
что-то не допер
 

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93
А можешь более подробно объяснить зачем?
что-то не допер
Предположим есть необходимость время от времени делать шабы читающие один и тот же конфиг.. шабы мелкие и разные, делать их можно быстро, но изрядно задалбывает создавать переменные, добавлять функционал чтения конфигурации. Пока это один конфиг - все еще более менее, т.к. можно подготовленный шаблон использовать, где это уже сделано. Но как только добавляется еще несколько конфигов - начинается пляска и путаница.
А так сниппет выполнил, он и переменные создал и данные в них прочитал. Поставил сниппет первым - он и дальнейшее чтение параметров в шабе обеспечит. Не нужно ручками ничего создавать и читать из файла.
Быстро наваял что нужно, используя уже созданные переменные, в зенку добавил и финита.
В общем небольшая автоматизация процесса создания автоматизации)

Ну а вторичный функционал - это банальное чтение параметров из инишника.. для случаев когда не используется бд и неохота заморачиваться с входными настройками.
 
  • Спасибо
Реакции: Nord, Sanekk и deopl

z@jivalo

Client
Регистрация
27.12.2016
Сообщения
798
Благодарностей
178
Баллы
43
интересная задумка, единый конфиг для всех шабов в одной рабочей папке.
была похожая мысль давно еще, но потом вылетела из головы
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 852
Баллы
113
Отличная инициатива и полезный сниппет! :-)
Надо будет его к какому-нибудь шаблону прикрутить и испытать в деле, а то у меня везде входящие настройки в виде конфигов, а это крайне неудобно (из ЗП копировать данные оттуда для отладки в ПМ - не веселое занятие, когда переменные по-умолчанию должны быть не production грубо говоря)
 

nik-n

Client
Регистрация
05.11.2016
Сообщения
229
Благодарностей
18
Баллы
18
Читает ini-шник лежащий рядом с шаблоном имя которого совпадает с именем шаблона. Либо задать конкретное имя файла в снипете.
А как абсолютный путь прописать в сниппет?
Шаблоны в разных папках лежат, и до конфига получается дотянуться можно только через абсолютный путь.
 

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93

nik-n

Client
Регистрация
05.11.2016
Сообщения
229
Благодарностей
18
Баллы
18

Вложения

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93

nik-n

Client
Регистрация
05.11.2016
Сообщения
229
Благодарностей
18
Баллы
18

nik-n

Client
Регистрация
05.11.2016
Сообщения
229
Благодарностей
18
Баллы
18
"CS0103 Имя config_path отсутствует в текущем контексте Строка 8 Столбец 70"
Надо было раскомментировать это
string config_file = "Config.ini";

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

Вложения

  • Спасибо
Реакции: DmitryAk

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 941
Благодарностей
4 331
Баллы
113
Когда я только познакомился с Зенкой сразу подумал - а нафиг нужны эти входящие настройки, надо же через ini. Но так и не сделал.
 

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93
а нафиг нужны эти входящие настройки
Чистое имхо - входные хороши для интерактивной работы, когда выбрал параметры запуска, стартанул, посмотрел что в итоге, выбрал другие, снова запустил..
 

Dimionix

Moderator
Регистрация
09.04.2011
Сообщения
3 068
Благодарностей
3 099
Баллы
113
Работа с переменными - это хорошо, но не помешала бы ещё работа со списками/таблицами (получение имён и создание).
Ковырял метод Object.GetType(), но через него, по видимому, реализовать не получится - нет нужных методов и свойств.
 

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93
К спискам и таблицам не добраться таким методом. ITables и ILists интерфейсы реализуют только get метод получения списка/таблицы по имени. Создание, список элементов, переименование и тд - где-то глубоко в недрах постера.
 
  • Спасибо
Реакции: Dimionix

nik-n

Client
Регистрация
05.11.2016
Сообщения
229
Благодарностей
18
Баллы
18
хех )
а как правильно в файле INI запихать в переменную код для С# кубика? записывать код в строку?
и еще, как потом вставить в C# кубик эту переменную? я так понимаю что это не просто вставить переменную и все.

ps:
о я смотрю вопрос мой прям в продолжение разговора)
да, вот с таблицами бы еще как то снюхать, и все - вообще красота была бы!
 
Последнее редактирование:

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93
Боюсь что это не получится. Если надо разделить код - его выносят в библиотеку и используют её.
 

nik-n

Client
Регистрация
05.11.2016
Сообщения
229
Благодарностей
18
Баллы
18
Боюсь что это не получится. Если надо разделить код - его выносят в библиотеку и используют её.
Да, такой финт не проканал )
А как это "его выносят в библиотеку?"
 

up_lvl

Client
Регистрация
02.09.2014
Сообщения
130
Благодарностей
52
Баллы
28
Хорошая идея. Искал тут по своему вопросу, наткнулся на этот топик как наиболее релевантный. Но у меня всё же немного другой кейс.
Идея состоит в том чтобы где-то хранить настройки подключения к БД в зависимости от переменной-триггера pull_type(принимает одно из двух значений production|test). Хочется прийти к такому стандартному кубику:




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

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 852
Баллы
113
Но вся загвоздка в том что в многопотоке нагрузка достаточно большая и через File.ReadAllText происходит параллельное обращение к файлу и следствие - креши
Очень странно. Я не спец в C#, но возможно данным методом файл может открываться и на запись (а нужно только чтение)?

Но вообще - стоит поискать ошибку в чем-то другом, вряд ли всё так плохо с данным методом. Скорее от парсинга json косяк может вылезти, чем оттуда, имхо.
 

up_lvl

Client
Регистрация
02.09.2014
Сообщения
130
Благодарностей
52
Баллы
28
Да, охрана отмена. Где-то накосячил с экспериментом. Всё ок.
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 852
Баллы
113
Обновил сниппет из стартпоста, чтобы появилась возможность правильно парсить следующие строки в 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);
}
 

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 852
Баллы
113

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 354
Благодарностей
3 279
Баллы
113
Можно избавиться от регулярок и связанних проверок, если задействовать уже имеющиеся возможности для работы с 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;
            }
        }
    }
}
 

Вложения

Последнее редактирование:

DmitryAk

Client
Регистрация
14.12.2016
Сообщения
860
Благодарностей
817
Баллы
93
Можно избавиться от регулярок и связанних проверок, если задействовать уже имеющиеся возможности для работы с ini-файлами.
Работа через WinAPI, но это лучше, чем регулярки.

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

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 354
Благодарностей
3 279
Баллы
113
в виде одного сниппета без необходимости подключения библиотек, правок в общем коде или uses.
Этот момент, я упустил :bk:
Вся цель сниппета в быстром подключении. Т.е. буквально бросили кубик, впихнули сниппет и пошли дальше. А если полноценно работать с инишником, то да, лучше через винапи.
Вообще можно и это впихнуть в метод, но в данном случае, для меня пока сложно, да и выглядеть будет тонной кода.
 
Последнее редактирование:

mig-z

Client
Регистрация
05.12.2014
Сообщения
303
Благодарностей
70
Баллы
28
Что то у меня месиво какое то создалось. Видимо в кодировке проблема но не уверен. Копировал код из 1го сообщения. Кодировка инишки UTF8 без BOM.
Скрин: https://s.mail.ru/9Zov/NmyWBZEAt
Проект во вложении.

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

Вложения

DevOps

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

inilim

Client
Регистрация
16.09.2017
Сообщения
441
Благодарностей
170
Баллы
43
Распарсить json разве не делает тоже самое? Он и переменные создает.
 
  • Спасибо
Реакции: bizzon

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