كيف يمكنني تضمين ملف YAML داخل ملف آخر؟
-
22-08-2019 - |
سؤال
إذن لدي ملفان YAML، "A" و"B" وأريد أن يتم إدراج محتويات A داخل B، إما مقسمة إلى بنية البيانات الموجودة، مثل المصفوفة، أو كطفل لعنصر، مثل القيمة لمفتاح تجزئة معين.
هل هذا ممكن على الإطلاق؟كيف؟إذا لم يكن الأمر كذلك، أي مؤشرات إلى مرجعية معيارية؟
المحلول
لا، YAML لا يتضمن أي نوع من "استيراد" أو "تشمل" بيان.
نصائح أخرى
سؤالك لا يطالب بحل بايثون، ولكن إليك حلًا يستخدمه بييامل.
يسمح لك PyYAML بإرفاق مُنشئات مخصصة (مثل !include
) إلى محمل YAML.لقد قمت بتضمين دليل جذر يمكن تعيينه بحيث يدعم هذا الحل مراجع الملفات النسبية والمطلقة.
الحل القائم على الفصل
إليك حلًا قائمًا على الفصل يتجنب المتغير الجذري العام لاستجابتي الأصلية.
انظر الى هذا جوهر للحصول على حل مماثل وأكثر قوة لـ Python 3 يستخدم فئة تعريفية لتسجيل المُنشئ المخصص.
import yaml
import os
class Loader(yaml.SafeLoader):
def __init__(self, stream):
self._root = os.path.split(stream.name)[0]
super(Loader, self).__init__(stream)
def include(self, node):
filename = os.path.join(self._root, self.construct_scalar(node))
with open(filename, 'r') as f:
return yaml.load(f, Loader)
Loader.add_constructor('!include', Loader.include)
مثال:
foo.yaml
a: 1
b:
- 1.43
- 543.55
c: !include bar.yaml
bar.yaml
- 3.6
- [1, 2, 3]
الآن يمكن تحميل الملفات باستخدام:
>>> with open('foo.yaml', 'r') as f:
>>> data = yaml.load(f, Loader)
>>> data
{'a': 1, 'b': [1.43, 543.55], 'c': [3.6, [1, 2, 3]]}
إذا كنت تستخدم نسخة أن symfony من YAML ، وهذا ممكن، مثل هذا:
imports:
- { resource: sub-directory/file.yml }
- { resource: sub-directory/another-file.yml }
ويشمل غير معتمدة مباشرة في YAML بقدر ما أعرف، سيكون لديك لتوفير آلية نفسك ولكن هذا عموما من السهل القيام به.
ولقد استخدمت YAML كلغة التكوين في بلدي تطبيقات الثعبان، وفي هذه الحالة غالبا ما تحدد اتفاقية من هذا القبيل:
>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]
وبعد ذلك في قانون بلدي (الثعبان) أفعل:
import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
cfg.update(yaml.load(open(inc)))
والجانب السلبي الوحيد هو أن المتغيرات في يشمل ستتجاوز دائما المتغيرات في الرئيسية، وليس هناك أي وسيلة لتغيير هذا الأسبقية عن طريق تغيير حيث "يشمل: يظهر عبارة في الملف main.yml
.في وجهة نظر مختلفة قليلا، YAML لا يدعم يتضمن كما ليست مصممة حقا كما حصريا كملف أساس المبرمجة. ما يمكن أن تشمل يعني إذا كنت حصلت عليه في استجابة لطلب AJAX؟
والتوسع في @ الجواب Josh_Bode، وهنا بلدي الحل PyYAML الخاصة، التي لديها ميزة كونها فئة فرعية مكتفية ذاتيا من yaml.Loader
. انها لا تعتمد على أي غلوبالس على مستوى الوحدة، أو على تعديل الحالة العالمية للوحدة yaml
.
import yaml, os
class IncludeLoader(yaml.Loader):
"""
yaml.Loader subclass handles "!include path/to/foo.yml" directives in config
files. When constructed with a file object, the root path for includes
defaults to the directory containing the file, otherwise to the current
working directory. In either case, the root path can be overridden by the
`root` keyword argument.
When an included file F contain its own !include directive, the path is
relative to F's location.
Example:
YAML file /home/frodo/one-ring.yml:
---
Name: The One Ring
Specials:
- resize-to-wearer
Effects:
- !include path/to/invisibility.yml
YAML file /home/frodo/path/to/invisibility.yml:
---
Name: invisibility
Message: Suddenly you disappear!
Loading:
data = IncludeLoader(open('/home/frodo/one-ring.yml', 'r')).get_data()
Result:
{'Effects': [{'Message': 'Suddenly you disappear!', 'Name':
'invisibility'}], 'Name': 'The One Ring', 'Specials':
['resize-to-wearer']}
"""
def __init__(self, *args, **kwargs):
super(IncludeLoader, self).__init__(*args, **kwargs)
self.add_constructor('!include', self._include)
if 'root' in kwargs:
self.root = kwargs['root']
elif isinstance(self.stream, file):
self.root = os.path.dirname(self.stream.name)
else:
self.root = os.path.curdir
def _include(self, loader, node):
oldRoot = self.root
filename = os.path.join(self.root, loader.construct_scalar(node))
self.root = os.path.dirname(filename)
data = yaml.load(open(filename, 'r'))
self.root = oldRoot
return data
بالنسبة لمستخدمي بايثون، يمكنك المحاولة pyyaml-include.
ثَبَّتَ
pip install pyyaml-include
الاستخدام
import yaml
from yamlinclude import YamlIncludeConstructor
YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir='/your/conf/dir')
with open('0.yaml') as f:
data = yaml.load(f, Loader=yaml.FullLoader)
print(data)
النظر في أن لدينا مثل هذا يامل الملفات:
├── 0.yaml
└── include.d
├── 1.yaml
└── 2.yaml
1.yaml
محتوى :
name: "1"
2.yaml
محتوى :
name: "2"
تضمين الملفات بالاسم
على المستوى الأعلى:
لو
0.yaml
كان:
!include include.d/1.yaml
سوف نحضر:
{"name": "1"}
في رسم الخرائط:
لو
0.yaml
كان:
file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
سوف نحضر:
file1:
name: "1"
file2:
name: "2"
في تسلسل:
لو
0.yaml
كان:
files:
- !include include.d/1.yaml
- !include include.d/2.yaml
سوف نحضر:
files:
- name: "1"
- name: "2"
ℹ ملحوظة:
يمكن أن يكون اسم الملف مطلقًا (مثل
/usr/conf/1.5/Make.yml
) أو قريب (مثل../../cfg/img.yml
).
تضمين الملفات بواسطة أحرف البدل
يمكن أن يحتوي اسم الملف على أحرف بدل بنمط الصدفة.سيتم تعيين البيانات المحملة من الملف (الملفات) التي تم العثور عليها بواسطة أحرف البدل بالتسلسل.
لو 0.yaml
كان:
files: !include include.d/*.yaml
سوف نحضر:
files:
- name: "1"
- name: "2"
ℹ ملحوظة:
- ل
Python>=3.5
, ، لوrecursive
حجة!include
يامل العلامة هيtrue
, ، النمط“**”
سوف يطابق أي ملفات وصفر أو أكثر من الدلائل والأدلة الفرعية.- باستخدام
“**”
قد يستهلك النمط الموجود في أشجار الدليل الكبيرة قدرًا هائلاً من الوقت بسبب البحث العودي.
لكي تمكن recursive
حجة، يجب علينا أن نكتب !include
العلامة في Mapping
أو Sequence
وضع:
- الحجج في
Sequence
وضع:
!include [tests/data/include.d/**/*.yaml, true]
- الحجج في
Mapping
وضع:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
ومما يؤسف له YAML لا يوفر هذا في مستواه.
ولكن إذا كنت تستخدم روبي، هناك جوهرة توفير وظائف تسألون عن طريق تمديد مكتبة روبي YAML: https://github.com/entwanderer/yaml_extend
وأعتقد أن الحل يستخدمه @ MAXY-B يبدو رائعا. ومع ذلك، فإنه لم ينجح بالنسبة لي مع شوائب المتداخلة. على سبيل المثال إذا يتضمن config_1.yaml config_2.yaml، والذي يتضمن config_3.yaml كان هناك مشكلة مع المحمل. ومع ذلك، إذا كنت مجرد نقطة الطبقة محمل جديدة لنفسها على الحمل، وأنها تعمل! على وجه التحديد، إذا كنا استبدال الدالة _include القديمة مع نسخة معدلة بشكل طفيف جدا:
def _include(self, loader, node):
oldRoot = self.root
filename = os.path.join(self.root, loader.construct_scalar(node))
self.root = os.path.dirname(filename)
data = yaml.load(open(filename, 'r'), loader = IncludeLoader)
self.root = oldRoot
return data
وبعد التفكير وأنا أتفق مع تعليقات أخرى، أن تحميل متداخل غير مناسب لYAML بشكل عام كما دفق الإدخال قد لا يكون ملف، ولكن من المفيد جدا!
وربما هذا يمكن أن تلهمك، في محاولة لمحاذاة إلى الاتفاقيات JBB:
https://docs.openstack.org /infra/jenkins-job-builder/definition.html#inclusion-tags
و
- job:
name: test-job-include-raw-1
builders:
- shell:
!include-raw: include-raw001-hello-world.sh
وستاندرد YAML 1.2 لا يتضمن أصلا هذه الميزة. مع ذلك العديد من تطبيقات توفر بعض تمديد للقيام بذلك.
وأقدم وسيلة لتحقيق ذلك مع جافا وsnakeyaml:1.24
(مكتبة جافا لتحليل / تنبعث منها ملفات YAML) الذي يسمح بإنشاء علامة YAML المخصصة لتحقيق الهدف التالي (سترون أنا استخدامه لتحميل أجنحة اختبار المعرفة في عدة ملفات YAML وأنني جعلت من العمل كقائمة يشمل لعقدة test:
الهدف):
# ... yaml prev stuff
tests: !include
- '1.hello-test-suite.yaml'
- '3.foo-test-suite.yaml'
- '2.bar-test-suite.yaml'
# ... more yaml document
وهنا هو جافا فئة واحدة أن يسمح تجهيز العلامة !include
. يتم تحميل الملفات من CLASSPATH (دليل الموارد مخضرم):
/**
* Custom YAML loader. It adds support to the custom !include tag which allows splitting a YAML file across several
* files for a better organization of YAML tests.
*/
@Slf4j // <-- This is a Lombok annotation to auto-generate logger
public class MyYamlLoader {
private static final Constructor CUSTOM_CONSTRUCTOR = new MyYamlConstructor();
private MyYamlLoader() {
}
/**
* Parse the only YAML document in a stream and produce the Java Map. It provides support for the custom !include
* YAML tag to split YAML contents across several files.
*/
public static Map<String, Object> load(InputStream inputStream) {
return new Yaml(CUSTOM_CONSTRUCTOR)
.load(inputStream);
}
/**
* Custom SnakeYAML constructor that registers custom tags.
*/
private static class MyYamlConstructor extends Constructor {
private static final String TAG_INCLUDE = "!include";
MyYamlConstructor() {
// Register custom tags
yamlConstructors.put(new Tag(TAG_INCLUDE), new IncludeConstruct());
}
/**
* The actual include tag construct.
*/
private static class IncludeConstruct implements Construct {
@Override
public Object construct(Node node) {
List<Node> inclusions = castToSequenceNode(node);
return parseInclusions(inclusions);
}
@Override
public void construct2ndStep(Node node, Object object) {
// do nothing
}
private List<Node> castToSequenceNode(Node node) {
try {
return ((SequenceNode) node).getValue();
} catch (ClassCastException e) {
throw new IllegalArgumentException(String.format("The !import value must be a sequence node, but " +
"'%s' found.", node));
}
}
private Object parseInclusions(List<Node> inclusions) {
List<InputStream> inputStreams = inputStreams(inclusions);
try (final SequenceInputStream sequencedInputStream =
new SequenceInputStream(Collections.enumeration(inputStreams))) {
return new Yaml(CUSTOM_CONSTRUCTOR)
.load(sequencedInputStream);
} catch (IOException e) {
log.error("Error closing the stream.", e);
return null;
}
}
private List<InputStream> inputStreams(List<Node> scalarNodes) {
return scalarNodes.stream()
.map(this::inputStream)
.collect(toList());
}
private InputStream inputStream(Node scalarNode) {
String filePath = castToScalarNode(scalarNode).getValue();
final InputStream is = getClass().getClassLoader().getResourceAsStream(filePath);
Assert.notNull(is, String.format("Resource file %s not found.", filePath));
return is;
}
private ScalarNode castToScalarNode(Node scalarNode) {
try {
return ((ScalarNode) scalarNode);
} catch (ClassCastException e) {
throw new IllegalArgumentException(String.format("The value must be a scalar node, but '%s' found" +
".", scalarNode));
}
}
}
}
}
مع سيمفوني, ، فإن تعامله مع yaml سيسمح لك بشكل غير مباشر بدمج ملفات yaml.الحيلة هي الاستفادة من parameters
خيار.على سبيل المثال:
common.yml
parameters:
yaml_to_repeat:
option: "value"
foo:
- "bar"
- "baz"
config.yml
imports:
- { resource: common.yml }
whatever:
thing: "%yaml_to_repeat%"
other_thing: "%yaml_to_repeat%"
ستكون النتيجة هي نفسها:
whatever:
thing:
option: "value"
foo:
- "bar"
- "baz"
other_thing:
option: "value"
foo:
- "bar"
- "baz"
وربما كان غير معتمد عندما سئل السؤال ولكن يمكنك استيراد ملف YAML أخرى إلى واحد:
imports: [/your_location_to_yaml_file/Util.area.yaml]
وعلى الرغم من أنني لم يكن لديك أي إشارة على الانترنت ولكن هذا يعمل بالنسبة لي.