_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
spiff++
เป็นทางแยกของ spiff ที่ให้ส่วนขยายที่เข้ากันได้กับ spiff โดยอิงตามเวอร์ชันล่าสุดที่นำเสนอชุดคุณสมบัติใหม่มากมายที่ยังไม่มีใน spiff การแก้ไขทั้งหมดที่ได้รับจากโปรเจ็กต์ spiff ดั้งเดิมจะรวมอยู่ใน spiff++ ด้วยเช่นกัน เนื่องจากจะไม่มีทางกลับไปยังฐานแหล่งที่มา spiff จึงมีการสร้างที่เก็บ spiff++ อิสระใหม่เพื่อพัฒนา spiff++ ต่อไปspiff เป็นเครื่องมือบรรทัดคำสั่งและระบบเทมเพลต YAML แบบไฮบริดในโดเมนที่ประกาศ ในขณะที่ระบบการสร้างเทมเพลตปกติประมวลผลไฟล์เทมเพลตโดยการแทนที่นิพจน์เทมเพลตด้วยค่าที่นำมาจากแหล่งข้อมูลภายนอก ในโดเมนหมายความว่ากลไกการสร้างเทมเพลตรู้เกี่ยวกับไวยากรณ์และโครงสร้างของเทมเพลตที่ประมวลผล ดังนั้นจึงสามารถรับค่าสำหรับนิพจน์เทมเพลตได้โดยตรงจากเอกสารที่ประมวลผล รวมถึงส่วนต่างๆ ที่แสดงโดยนิพจน์เทมเพลตด้วย
ตัวอย่างเช่น:
resource :
name : bosh deployment
version : 25
url : (( "http://resource.location/bosh?version=" version ))
description : (( "This document describes a " name " located at " url ))
แทนที่จะใช้เฉพาะแหล่งที่มาของค่าภายนอก spiff จัดเตรียมกลไกการผสานเพื่อผสานเทมเพลตกับ stub ที่ผสานจำนวนเท่าใดก็ได้เพื่อสร้างเอกสารขั้นสุดท้าย
เป็นเครื่องมือบรรทัดคำสั่งและระบบเทมเพลต YAML ที่ประกาศ ซึ่งออกแบบมาเป็นพิเศษสำหรับการสร้างรายการปรับใช้ (เช่น รายการ BOSH, Kubernetes หรือ Landscaper)
นอกจาก CLI แล้ว ยังมีไลบรารี golang ที่ทำให้สามารถใช้การประมวลผลเทมเพลต spiff ในโปรแกรม GO ใดๆ ก็ได้ (เช่น Landscaper)
เอ็นจิ้นการสร้างเทมเพลตช่วยให้สามารถเข้าถึงระบบไฟล์ตามระบบไฟล์เสมือนที่กำหนดค่าได้หรือระบบกระบวนการเพื่อดำเนินการคำสั่งและรวมเอาต์พุตเข้ากับการประมวลผลเทมเพลต
สารบัญ:
<<if:
<<switch:
<<type:
<<for:
<<merge:
สามารถดาวน์โหลดไบนารีปฏิบัติการที่เผยแพร่อย่างเป็นทางการได้ผ่านทาง Github release สำหรับเครื่อง Darwin, Linux และ PowerPC (และเครื่องเสมือน)
การขึ้นต่อกันของ spiff บางส่วนมีการเปลี่ยนแปลงตั้งแต่การเปิดตัวอย่างเป็นทางการครั้งล่าสุด และ spiff จะไม่ได้รับการอัปเดตเพื่อให้ทันกับการขึ้นต่อกันเหล่านี้ การขึ้นต่อกันเหล่านี้ได้รับการแก้ไขหรือคัดลอกลงในฐานโค้ดท้องถิ่น
spiff merge template.yml [template2.yml ...]
รวมไฟล์เทมเพลตจำนวนมากเข้าไว้ในรายการเดียว แล้วพิมพ์ออกมา
ดู 'ภาษาเทมเพลต dynaml' สำหรับรายละเอียดของไฟล์เทมเพลต หรือ example/ subdir สำหรับตัวอย่างที่ซับซ้อนมากขึ้น
ตัวอย่าง:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
สามารถอ่านไฟล์หนึ่งไฟล์จากอินพุตมาตรฐานได้โดยใช้ชื่อไฟล์ -
อาจใช้ได้เพียงครั้งเดียว ซึ่งช่วยให้สามารถใช้ spiff เป็นส่วนหนึ่งของไปป์ไลน์เพื่อประมวลผลสตรีมเดียวหรือประมวลผลสตรีมตามเทมเพลต/สตับหลายรายการ
ไฟล์เทมเพลต (อาร์กิวเมนต์แรก) อาจเป็นกระแสข้อมูลหลายเอกสารที่มีเอกสาร YAML หลายฉบับ คั่นด้วยบรรทัดที่มีเพียง ---
เอกสาร YAML แต่ละฉบับจะได้รับการประมวลผลอย่างแยกจากกันด้วยไฟล์ stub ที่กำหนด ผลลัพธ์ที่ได้คือกระแสของเอกสารที่ประมวลผลในลำดับเดียวกัน หากโหนดรูทของเอกสารถูกทำเครื่องหมายเป็นแบบชั่วคราว เอกสารจะถูกละเว้นจากสตรีมเอาต์พุต ตัวอย่างเช่น สามารถใช้เพื่อสร้างรายการ kubernetes ที่จะใช้โดย kubectl
คำสั่ง merge
มีหลายตัวเลือก:
ตัวเลือก --partial
ส่วน หากได้รับตัวเลือกนี้ spiff จะจัดการการประเมินนิพจน์ที่ไม่สมบูรณ์ ข้อผิดพลาดทั้งหมดจะถูกละเว้นและส่วนที่แก้ไขไม่ได้ของเอกสาร yaml จะถูกส่งกลับเป็นสตริง
ด้วยตัวเลือก --json
ผลลัพธ์จะอยู่ในรูปแบบ JSON แทนที่จะเป็น YAML
ตัวเลือก --path <path>
สามารถใช้เพื่อส่งออกเส้นทางที่ซ้อนกัน แทนที่จะเป็นเอกสารที่ประมวลผลเสร็จสมบูรณ์
หากเอาต์พุตเป็นรายการ ตัวเลือก --split
เอาต์พุตทุกองค์ประกอบรายการเป็นเอกสารแยกกัน รูปแบบ yaml ใช้ตามปกติ ---
เป็นบรรทัดคั่น รูปแบบ json ส่งออกลำดับของเอกสาร json หนึ่งรายการต่อบรรทัด
ด้วย --select <field path>
คุณสามารถเลือกฟิลด์เฉพาะของเอกสารที่ประมวลผลสำหรับเอาต์พุตได้
ด้วย --evaluate <dynaml expression>
คุณสามารถประเมินนิพจน์ dynaml ที่กำหนดบนเอกสารที่ประมวลผลสำหรับเอาต์พุตได้ นิพจน์จะได้รับการประเมินก่อนที่จะใช้เส้นทางการเลือก ซึ่งจะส่งผลต่อผลการประเมิน
ตัวเลือก --state <path>
เปิดใช้งานการสนับสนุนสถานะของ spiff หากไฟล์ที่ระบุมีอยู่ ไฟล์นั้นจะถูกวางไว้ด้านบนของรายการต้นขั้วที่กำหนดค่าไว้สำหรับไฟล์ที่กำหนด ไฟล์นั้นจะถูกวางไว้ด้านบนของรายการต้นขั้วที่กำหนดค่าไว้สำหรับการประมวลผลการผสาน นอกจากนี้ในเอาต์พุตของเอกสารที่ประมวลผลแล้ว ยังถูกกรองสำหรับโหนดที่ทำเครื่องหมายด้วยเครื่องหมาย &state
เอกสารที่กรองแล้วนี้จะถูกจัดเก็บไว้ภายใต้ไฟล์ที่ระบุ โดยบันทึกไฟล์สถานะเก่าด้วยส่วนต่อท้าย .bak
สามารถใช้ร่วมกับการรวมด้วยตนเองตามที่เสนอโดยไลบรารียูทิลิตี้ของรัฐ
ด้วยตัวเลือก --bindings <path>
คุณสามารถระบุไฟล์ yaml ซึ่งมีเนื้อหาที่ใช้เพื่อสร้างการเชื่อมโยงเพิ่มเติมสำหรับการประมวลผล เอกสาร yaml จะต้องประกอบด้วยแผนที่ แต่ละคีย์จะถูกใช้เป็นการเชื่อมโยงเพิ่มเติม เอกสารการผูกไม่ได้รับการประมวลผล ค่าจะถูกใช้ตามที่กำหนดไว้
ด้วยตัวเลือก --tag <tag>:<path>
คุณสามารถระบุไฟล์ yaml ซึ่งมีเนื้อหาที่ใช้เป็นค่าสำหรับแท็กสากลที่กำหนดไว้ล่วงหน้า (ดูแท็ก) แท็กสามารถเข้าถึงได้โดยนิพจน์อ้างอิงของแบบฟอร์ม <tag>::<ref>
ตรงกันข้ามกับการเชื่อมโยงเนื้อหาที่ติดแท็กไม่ได้แข่งขันกับโหนดในเอกสาร แต่ใช้เนมสเปซอ้างอิงอื่น
ด้วยตัวเลือก --define <key>=<value>
(ชวเลข -D
) สามารถระบุค่าการเชื่อมโยงเพิ่มเติมบนบรรทัดคำสั่งที่แทนที่ค่าการเชื่อมโยงจากไฟล์การเชื่อมโยง ตัวเลือกนี้อาจเกิดขึ้นหลายครั้ง
หาก คีย์ มีจุด ( .
) คีย์นั้นจะถูกตีความว่าเป็นนิพจน์เส้นทางเพื่ออธิบายฟิลด์ในค่าแผนที่เชิงลึก จุด (และ ก่อนจุด) สามารถหลีกเลี่ยงได้โดย
เพื่อเก็บไว้ในชื่อฟิลด์
ตัวเลือก --preserve-escapes
จะคงการ Escape สำหรับนิพจน์ dynaml และคำสั่งผสานรายการ/แผนที่ ตัวเลือกนี้สามารถใช้ได้หากต้องการขั้นตอนการประมวลผลเพิ่มเติมของผลการประมวลผลที่มี spiff
ตัวเลือก --preserve-temporary
จะรักษาฟิลด์ที่ทำเครื่องหมายว่าชั่วคราวไว้ในเอกสารขั้นสุดท้าย
ตัวเลือก --features=<featurelist>
จะเปิดใช้งานคุณสมบัติที่กำหนดนี้ คุณสมบัติใหม่ที่ไม่เข้ากันกับลักษณะการทำงานเก่าจะต้องเปิดใช้งานอย่างชัดเจน โดยทั่วไปแล้วฟีเจอร์เหล่านั้นจะไม่ทำลายพฤติกรรมทั่วไป แต่จะแนะนำการตีความเฉพาะสำหรับค่า yaml ที่เคยใช้เป็นค่าปกติมาก่อน
ไลบรารีโฟลเดอร์มีไลบรารียูทิลิตี้ที่มีประโยชน์ นอกจากนี้ยังสามารถใช้เป็นตัวอย่างสำหรับพลังของเครื่องมือสร้างเทมเพลตนี้ได้
spiff diff manifest.yml other-manifest.yml
แสดงความแตกต่างเชิงโครงสร้างระหว่างรายการปรับใช้สองรายการ ที่นี่รองรับสตรีมที่มีเอกสารหลายฉบับด้วย เพื่อระบุว่าไม่มีความแตกต่าง จำนวนเอกสารในทั้งสองสตรีมจะต้องเท่ากัน และแต่ละเอกสารในสตรีมแรกจะต้องไม่แตกต่างกันเมื่อเปรียบเทียบกับเอกสารที่มีดัชนีเดียวกันในสตรีมที่สอง ความแตกต่างที่พบจะแสดงสำหรับแต่ละเอกสารแยกกัน
ไม่เหมือนกับเครื่องมือ diffing พื้นฐานและแม้แต่ bosh diff
คำสั่งนี้มีความรู้เชิงความหมายของรายการการปรับใช้ และไม่ใช่แค่แบบข้อความเท่านั้น ตัวอย่างเช่น หากสองรายการเหมือนกัน ยกเว้นรายการงานบางอย่างในลำดับที่แตกต่างกัน spiff diff
จะตรวจพบสิ่งนี้ เนื่องจากใบสั่งงานมีความสำคัญในรายการ ในทางกลับกัน หากรายการสองรายการแตกต่างกันเพียงตามลำดับของพูลทรัพยากร ก็จะให้ผลและค่าต่างว่างเนื่องจากลำดับพูลทรัพยากรไม่สำคัญสำหรับการปรับใช้จริงๆ
นอกจากนี้ยังแตกต่างจาก bosh diff
ตรงที่คำสั่งนี้ไม่ได้แก้ไขไฟล์ใดไฟล์หนึ่ง
มีไว้เพื่อตรวจสอบความแตกต่างระหว่างการปรับใช้ครั้งหนึ่งกับครั้งต่อไป
การไหลทั่วไป:
$ spiff merge template.yml [templates...] > deployment.yml
$ bosh download manifest [deployment] current.yml
$ spiff diff deployment.yml current.yml
$ bosh deployment deployment.yml
$ bosh deploy
spiff convert --json manifest.yml
คำสั่งย่อย convert
สามารถใช้เพื่อแปลงไฟล์อินพุตเป็น json หรือเพียงเพื่อทำให้ลำดับของฟิลด์เป็นปกติ ตัวเลือกที่ใช้ได้คือ --json
, --path
, --split
หรือ --select
ตามความหมายสำหรับคำสั่ง merge
spiff encrypt secret.yaml
คำสั่งย่อย encrypt
สามารถใช้เพื่อเข้ารหัสหรือถอดรหัสข้อมูลตามฟังก์ชัน encrypt
dynaml รหัสผ่านสามารถกำหนดให้เป็นอาร์กิวเมนต์ที่สอง หรือนำมาจากตัวแปรสภาพแวดล้อม SPIFF_ENCRYPTION_KEY
อาร์กิวเมนต์สุดท้ายสามารถใช้เพื่อส่งผ่านวิธีการเข้ารหัส (ดูฟังก์ชัน encrypt
)
ข้อมูลถูกนำมาจากไฟล์ที่ระบุ ถ้าได้รับ -
จะถูกอ่านจาก stdin
หากระบุตัวเลือก -d
ข้อมูลจะถูกถอดรหัส ไม่เช่นนั้นข้อมูลจะถูกอ่านเป็นเอกสาร yaml และผลลัพธ์ที่เข้ารหัสจะถูกพิมพ์
คุณสมบัติใหม่ที่ไม่เข้ากันกับลักษณะการทำงานเก่าจะต้องเปิดใช้งานอย่างชัดเจน โดยทั่วไปแล้วฟีเจอร์เหล่านั้นจะไม่ทำลายพฤติกรรมทั่วไป แต่จะแนะนำการตีความเฉพาะสำหรับค่า yaml ที่เคยใช้เป็นค่าปกติมาก่อน และจึงสามารถทำลายกรณีการใช้งานที่มีอยู่ได้
ปัจจุบันรองรับฟีเจอร์แฟล็กต่อไปนี้:
คุณสมบัติ | เนื่องจาก | สถานะ | ความหมาย |
---|---|---|---|
interpolation | 1.7.0-เบต้า-1 | อัลฟ่า | dynaml ซึ่งเป็นส่วนหนึ่งของสตริง yaml |
control | 1.7.0-เบต้า-4 | อัลฟ่า | โครงสร้างการควบคุมที่ใช้ yaml |
สามารถสอบถามค่าสถานะคุณลักษณะที่ใช้งานอยู่ได้โดยใช้ฟังก์ชัน dynaml features()
เป็นรายการสตริง ถ้าฟังก์ชันนี้ถูกเรียกด้วยอาร์กิวเมนต์สตริง จะคืนค่าว่าคุณลักษณะที่กำหนดนั้นเปิดใช้งานอยู่หรือไม่
คุณลักษณะต่างๆ สามารถเปิดใช้งานได้โดยบรรทัดคำสั่งโดยใช้ตัวเลือก --features
โดยไลบรารี go โดยใช้ฟังก์ชัน WithFeatures
หรือโดยทั่วไปโดยการตั้งค่าตัวแปรสภาพแวดล้อม SPIFF_FEATURES
ให้เป็นรายการคุณลักษณะ การตั้งค่านี้ถูกใช้เป็นค่าเริ่มต้นเสมอ ด้วยการใช้การตั้งค่า Plain()
spiff จากไลบรารี go ตัวแปรสภาพแวดล้อมทั้งหมดจะถูกละเว้น
คุณลักษณะสามารถระบุตามชื่อหรือตามชื่อที่ขึ้นต้นด้วยคำนำหน้า no
เพื่อปิดใช้งาน
โฟลเดอร์ไลบรารีประกอบด้วยไลบรารีเทมเพลต spiff ที่มีประโยชน์ โดยพื้นฐานแล้วเป็นเพียง stubs ที่เพิ่มเข้าไปในรายการไฟล์ผสานเพื่อเสนอฟังก์ชั่นยูทิลิตี้สำหรับการประมวลผลการผสาน
Spiff ใช้ภาษาเทมเพลตที่ประกาศและไม่มีตรรกะที่เรียกว่า 'dynaml' (yaml แบบไดนามิก)
รับประกันว่าทุกโหนด dynaml จะแปลงเป็นโหนด YAML มัน ไม่ใช่ การแก้ไขสตริง สิ่งนี้ทำให้นักพัฒนาไม่ต้องคิดว่าค่าจะแสดงผลในเทมเพลตผลลัพธ์อย่างไร
โหนด dynaml จะปรากฏในไฟล์ .yml เป็นสตริงที่แสดงถึงนิพจน์ที่ล้อมรอบด้วยวงเล็บสองวงเล็บ (( <dynaml> ))
สามารถใช้เป็นค่าของแผนที่หรือรายการในรายการได้ นิพจน์อาจครอบคลุมหลายบรรทัด ไม่ว่าในกรณีใด ค่าสตริง yaml จะต้องไม่ ลงท้ายด้วยการขึ้นบรรทัดใหม่ (เช่น การใช้ |-
)
หากค่าที่อยู่ในวงเล็บไม่ควรถูกตีความว่าเป็นนิพจน์ dynaml และเก็บไว้ตามที่ปรากฏในเอาต์พุต ค่านั้นสามารถหลีกได้ด้วยเครื่องหมายอัศเจรีย์โดยตรงหลังวงเล็บเปิด
ตัวอย่างเช่น ((! .field ))
แมปกับค่าสตริง (( .field ))
และ ((!! .field ))
แมปกับค่าสตริง ((! .field ))
ต่อไปนี้เป็นรายการนิพจน์ dynaml ที่สมบูรณ์:
(( foo ))
ค้นหาคีย์ 'foo' ที่ใกล้ที่สุด (เช่น การกำหนดขอบเขตคำศัพท์) ในเทมเพลตปัจจุบันแล้วนำเข้ามา
เช่น:
fizz :
buzz :
foo : 1
bar : (( foo ))
bar : (( foo ))
foo : 3
bar : (( foo ))
ตัวอย่างนี้จะแก้ไขเป็น:
fizz :
buzz :
foo : 1
bar : 1
bar : 3
foo : 3
bar : 3
ข้อมูลต่อไปนี้จะไม่ได้รับการแก้ไขเนื่องจากชื่อคีย์เหมือนกับค่าที่จะรวมเข้าด้วยกัน:
foo : 1
hi :
foo : (( foo ))
(( foo.bar.[1].baz ))
มองหาคีย์ 'foo' ที่ใกล้ที่สุด จากนั้นตามด้วย .bar.[1].baz
เส้นทางคือลำดับขั้นตอนที่คั่นด้วยจุด ขั้นตอนคือคำสำหรับแผนที่ หรือตัวเลขที่ล้อมรอบด้วยวงเล็บสำหรับการทำดัชนีรายการ ดัชนีอาจเป็นลบ (ลบตามด้วยตัวเลข) ดัชนีเชิงลบจะถูกนำมาจากจุดสิ้นสุดของรายการ (ดัชนีที่มีประสิทธิภาพ = ดัชนี + ความยาว (รายการ))
เส้นทางที่ไม่สามารถแก้ไขได้นำไปสู่ข้อผิดพลาดในการประเมิน หากคาดว่าบางครั้งจะไม่มีการอ้างอิง ก็ควรใช้ร่วมกับ '||' (ดูด้านล่าง) เพื่อรับประกันความละเอียด
หมายเหตุ : ไวยากรณ์ dynaml ได้รับการปรับปรุงใหม่เพื่อเปิดใช้งานไวยากรณ์ดัชนีตามปกติในขณะนี้ แทนที่จะเป็น foo.bar.[1]
foo.bar[1]
สามารถใช้งานได้แล้ว
หมายเหตุ : การอ้างอิงจะอยู่ในเทมเพลตหรือต้นขั้วเสมอ และลำดับไม่สำคัญ คุณสามารถอ้างถึงโหนดไดนามิกอื่นและถือว่าได้รับการแก้ไขแล้ว และโหนดอ้างอิงก็จะแก้ไขในที่สุดเมื่อโหนดที่ขึ้นต่อกันแก้ไขแล้ว
เช่น:
properties :
foo : (( something.from.the.stub ))
something : (( merge ))
สิ่งนี้จะแก้ไขได้ตราบใดที่ 'บางสิ่ง' สามารถแก้ไขได้ และตราบใดที่มันนำมาซึ่งสิ่งนี้:
from :
the :
stub : foo
หากเส้นทางเริ่มต้นด้วยจุด ( .
) เส้นทางจะถูกประเมินจากรากของเอกสารเสมอ หากรูทเอกสารเป็นรายการ ระดับแผนที่แรกจะถูกนำมาใช้เพื่อแก้ไขนิพจน์พาธ หากขึ้นต้นด้วย .__map
สามารถใช้เพื่อหลีกเลี่ยงความจำเป็นในการใช้ดัชนีรายการของตัวเอง (เช่น .[1].path
) ซึ่งอาจเปลี่ยนแปลงได้หากมีการเพิ่มรายการ
รายการรายการที่ประกอบด้วยแผนที่พร้อมฟิลด์ name
สามารถแก้ไขได้โดยตรงด้วยค่าชื่อซึ่งเป็นส่วนประกอบของเส้นทาง
หมายเหตุ : นอกจากนี้ยังใช้ได้กับเส้นทางสัมบูรณ์สำหรับเอกสารรายการด้วย
เช่น:
ยุคของอลิซใน
list :
- name : alice
age : 25
สามารถอ้างอิงได้โดยใช้เส้นทาง list.alice.age
แทน list[0].age
ตามค่าเริ่มต้น ฟิลด์ที่มี name
จะถูกใช้เป็นฟิลด์คีย์ หากควรใช้ฟิลด์อื่นเป็นฟิลด์คีย์ ก็สามารถทำเครื่องหมายในรายการหนึ่งเป็นคีย์ได้ โดยนำชื่อฟิลด์ด้วย key:
คำหลักนี้ถูกลบออกจากการประมวลผลและจะไม่เป็นส่วนหนึ่งของผลการประมวลผลขั้นสุดท้าย
เช่น:
list :
- key:person : alice
age : 25
alice : (( list.alice ))
จะได้รับการแก้ไข
list :
- person : alice
age : 25
alice :
person : alice
age : 25
ฟิลด์คีย์ใหม่นี้จะถูกสังเกตในระหว่างการรวมรายการด้วย
หากฟิลด์คีย์ที่เลือกเริ่มต้นด้วย !
คุณลักษณะหลักถูกปิดใช้งาน เครื่องหมายอัศเจรีย์จะถูกลบออกจากชื่อฟิลด์ที่มีผลเช่นกัน
หากค่าสำหรับฟิลด์คีย์ไม่เหมือนกัน ระบบจะปิดใช้งานด้วยเช่นกัน
(( foo.[bar].baz ))
มองหาคีย์ 'foo' ที่ใกล้ที่สุด จากนั้นไล่ตามไปยังฟิลด์ที่อธิบายโดย bar
นิพจน์ จากนั้นไปที่ .baz
ดัชนีอาจเป็นค่าคงที่จำนวนเต็ม (ไม่ต้องเว้นวรรค) ตามที่อธิบายไว้ในส่วนสุดท้าย แต่อาจเป็นนิพจน์ dynaml ตามอำเภอใจด้วย (แม้จะเป็นจำนวนเต็ม แต่มีช่องว่าง) หากนิพจน์ประเมินเป็นสตริง นิพจน์จะค้นหาฟิลด์เฉพาะ หากนิพจน์ประเมินเป็นจำนวนเต็ม องค์ประกอบอาร์เรย์ที่มีดัชนีนี้จะได้รับการแก้ไข จุด ( .
) ที่อยู่ด้านหน้าตัวดำเนินการดัชนีเป็นทางเลือก
เช่น:
properties :
name : alice
foo : (( values.[name].bar ))
values :
alice :
bar : 42
สิ่งนี้จะแก้ไข foo
เป็นค่า 42
ดัชนีไดนามิกอาจอยู่ที่ส่วนท้ายของนิพจน์ (ไม่มี .bar
)
โดยพื้นฐานแล้ว นี่เป็นวิธีที่ง่ายกว่าในการแสดงบางอย่างเช่น eval("values." name ".bar")
หากนิพจน์ประเมินค่ารายการ องค์ประกอบรายการ (สตริงหรือจำนวนเต็ม) จะถูกนำมาใช้เป็นองค์ประกอบเส้นทางเพื่อเข้าถึงฟิลด์ที่ลึกลงไป
เช่น:
properties :
name :
- foo
- bar
foo : (( values.[name] ))
values :
foo :
bar : 42
แก้ไข foo
อีกครั้งเป็นค่า 42
หมายเหตุ : ตัวดำเนินการดัชนีสามารถใช้ได้กับองค์ประกอบรูท ( .[index]
) เช่นกัน
เป็นไปได้ที่จะระบุดัชนีที่คั่นด้วยเครื่องหมายจุลภาคหลายรายการให้กับรายการต่อเนื่องกัน ( foo[0][1]
เทียบเท่ากับ `foo[0,1]) ในกรณีเช่นนี้ ดัชนีอาจไม่อยู่ในรายการอีกต่อไป
(( list.[1..3] ))
นิพจน์สไลซ์สามารถใช้เพื่อแยกรายการย่อยเฉพาะจากนิพจน์รายการได้ ช่วง start ..
end แยกรายการความยาว end-start+1 พร้อมองค์ประกอบจากดัชนี start to end หากดัชนีเริ่มต้นเป็นลบ ชิ้นจะถูกนำมาจากจุดสิ้นสุดของรายการจาก length+start ถึง length+end หากดัชนีสิ้นสุดต่ำกว่าดัชนีเริ่มต้น ผลลัพธ์จะเป็นอาร์เรย์ว่าง
เช่น:
list :
- a
- b
- c
foo : (( list.[1..length(list) - 1] ))
ดัชนีเริ่มต้นหรือสิ้นสุดอาจถูกละเว้น จากนั้นเลือกตามขนาดจริงของรายการ ดังนั้น list.[1..length(list)]
จึงเทียบเท่ากับ list.[1..]
ประเมิน foo
ไปยังรายการ [b,c]
(( 1.2e4 ))
รองรับตัวเลขจำนวนเต็มและค่าทศนิยม
(( "foo" ))
สตริงลิเทอรัล รองรับการเข้ารหัสสตริง json ทั้งหมด (สำหรับตัวอย่าง n
, "
หรือ uxxxx
)
(( [ 1, 2, 3 ] ))
รายการตามตัวอักษร องค์ประกอบรายการอาจเป็นนิพจน์อีกครั้ง มีรายการตัวอักษรพิเศษ [1 .. -1]
ที่สามารถใช้เพื่อแก้ไขช่วงตัวเลขที่เพิ่มขึ้นหรือลดลงในรายการ
เช่น:
list : (( [ 1 .. -1 ] ))
อัตราผลตอบแทน
list :
- 1
- 0
- -1
(( { "alice" = 25 } ))
ตัวอักษรแผนที่สามารถใช้เพื่ออธิบายแผนที่โดยเป็นส่วนหนึ่งของนิพจน์ dynaml ทั้งคีย์และค่าอาจเป็นนิพจน์อีกครั้ง โดยที่นิพจน์คีย์ต้องประเมินเป็นสตริง วิธีนี้ทำให้สามารถสร้างแผนที่ด้วยคีย์ที่ไม่คงที่ได้ ตัวดำเนินการมอบหมาย =
ได้รับการเลือกแทนเครื่องหมายโคลอนปกติ :
อักขระที่ใช้ใน yaml เนื่องจากจะส่งผลให้เกิดความขัดแย้งกับไวยากรณ์ yaml
ตัวอักษรแผนที่อาจประกอบด้วยการกำหนดฟิลด์จำนวนเท่าใดก็ได้โดยคั่นด้วยเครื่องหมาย ,
เช่น:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
อัตราผลตอบแทน
name : peter
age : 23
map :
alice : {}
peter : 23
อีกวิธีหนึ่งในการเขียนรายการตามนิพจน์คือฟังก์ชัน makemap
และ list_to_map
(( ( "alice" = 25 ) alice ))
สำนวนใดๆ อาจถูกนำหน้าด้วย ตัวอักษรขอบเขต ที่ชัดเจนจำนวนเท่าใดก็ได้ ข้อมูลขอบเขตจะอธิบายแผนที่ที่มีค่าสำหรับความละเอียดอ้างอิงสัมพัทธ์ของนิพจน์ (ขอบเขตคงที่) มันสร้างการเชื่อมโยงท้องถิ่นเพิ่มเติมสำหรับชื่อที่กำหนด
ขอบเขตตามตัวอักษรอาจประกอบด้วยการกำหนดฟิลด์จำนวนเท่าใดก็ได้ โดยคั่นด้วย ,
จุลภาค คีย์และค่าถูกกำหนดโดยนิพจน์ ในขณะที่นิพจน์คีย์ต้องประเมินเป็นสตริง นิพจน์ทั้งหมดได้รับการประเมินในขอบเขตภายนอกถัดไป ซึ่งหมายความว่าการตั้งค่าในภายหลังในขอบเขต จะไม่สามารถ ใช้การตั้งค่าก่อนหน้าในขอบเขตตามตัวอักษรของขอบเขตเดียวกันได้
เช่น:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
อัตราผลตอบแทน
scoped : 51
ชื่อฟิลด์อาจแสดงด้วยสัญลักษณ์ ( $
name )
(( foo bar ))
นิพจน์การต่อข้อมูลที่ใช้ในการต่อลำดับของนิพจน์ dynaml
(( "foo" bar ))
การต่อข้อมูล (โดยที่ bar เป็น dynaml expr อื่น) ลำดับใดๆ ของค่าแบบง่าย (สตริง จำนวนเต็ม และบูลีน) สามารถต่อเข้าด้วยกันได้ โดยกำหนดโดยนิพจน์ dynaml ใดๆ
เช่น:
domain : example.com
uri : (( "https://" domain ))
ในตัวอย่างนี้ uri
จะแก้ไขเป็นค่า "https://example.com"
(( [1,2] bar ))
การต่อรายการเข้าด้วยกันเป็นนิพจน์ (โดยที่ bar เป็น dynaml expr อื่น) ลำดับของรายการใดๆ สามารถต่อเข้าด้วยกันได้ โดยกำหนดโดยนิพจน์ dynaml ใดๆ
เช่น:
other_ips : [ 10.0.0.2, 10.0.0.3 ]
static_ips : (( ["10.0.1.2","10.0.1.3"] other_ips ))
ในตัวอย่างนี้ static_ips
จะแก้ไขเป็นค่า [ 10.0.1.2, 10.0.1.3, 10.0.0.2, 10.0.0.3 ]
หากนิพจน์ที่สองประเมินเป็นค่าอื่นที่ไม่ใช่รายการ (จำนวนเต็ม บูลีน สตริง หรือแมป) ค่าดังกล่าวจะถูกผนวกเข้ากับรายการแรก
เช่น:
foo : 3
bar : (( [1] 2 foo "alice" ))
ให้ผลตอบแทนรายการ [ 1, 2, 3, "alice" ]
สำหรับ bar
(( map1 map2 ))
การต่อแผนที่เข้าด้วยกันเป็นการแสดงออก ลำดับใดๆ ของแผนที่สามารถต่อเข้าด้วยกันได้ โดยกำหนดโดยนิพจน์ dynaml ใดๆ ดังนั้นรายการจะถูกรวมเข้าด้วยกัน รายการที่มีคีย์เดียวกันจะถูกเขียนทับจากซ้ายไปขวา
เช่น:
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat : (( foo bar ))
อัตราผลตอบแทน
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat :
alice : 24
bob : 26
paul : 27
(( auto ))
การคำนวณมูลค่าอัตโนมัติตามบริบท
ในแอตทริบิวต์ 'ขนาด' ของกลุ่มทรัพยากร หมายถึงการคำนวณตามอินสแตนซ์รวมของงานทั้งหมดที่ประกาศตัวเองว่าอยู่ในกลุ่มทรัพยากรปัจจุบัน
เช่น:
resource_pools :
- name : mypool
size : (( auto ))
jobs :
- name : myjob
resource_pool : mypool
instances : 2
- name : myotherjob
resource_pool : mypool
instances : 3
- name : yetanotherjob
resource_pool : otherpool
instances : 3
ในกรณีนี้ ขนาดพูลทรัพยากรจะแก้ไขเป็น '5'
(( merge ))
นำเส้นทางปัจจุบันเข้ามาจากไฟล์ stub ที่กำลังรวมเข้าด้วยกัน
เช่น:
foo :
bar :
baz : (( merge ))
จะพยายามนำเข้า foo.bar.baz
จาก stub แรกหรืออันที่สอง ฯลฯ โดยส่งคืนค่าจาก stub สุดท้ายที่ให้มา
หากไม่ได้กำหนดค่าที่เกี่ยวข้อง ก็จะส่งกลับค่าศูนย์ สิ่งนี้มีความหมายเหมือนกับนิพจน์อ้างอิง ไม่มีการผสานเป็นเทมเพลตที่ยังไม่ได้รับการแก้ไข ดู ||
-
<<: (( merge ))
การรวมแผนที่หรือรายการที่มีเนื้อหาขององค์ประกอบเดียวกันที่พบในต้นขั้วบางส่วน
** ข้อควรสนใจ ** merge
แบบนี้มีปัญหาความเข้ากันได้ ในเวอร์ชันก่อน 1.0.8 นิพจน์นี้ไม่เคยถูกแยกวิเคราะห์ มีเพียงการมีอยู่ของคีย์ <<:
เท่านั้นที่เกี่ยวข้อง ดังนั้นจึงมักจะมีการใช้ <<: (( merge ))
โดยที่ <<: (( merge || nil ))
หมายถึง ตัวแปรแรกจะต้องมีเนื้อหาในต้นขั้วอย่างน้อยหนึ่งต้น (เช่นเคยสำหรับตัวดำเนินการผสาน) ขณะนี้นิพจน์นี้ได้รับการประเมินอย่างถูกต้อง แต่จะทำให้ชุดเทมเพลตรายการที่มีอยู่เสียหาย ซึ่งใช้ตัวแปรแรก แต่หมายถึงชุดที่สอง ดังนั้นกรณีนี้จะได้รับการจัดการอย่างชัดเจนเพื่ออธิบายการผสานที่เป็นทางเลือก หากการผสานที่จำเป็นจริงๆ นั้นหมายถึงต้องมีตัวระบุที่ชัดเจนเพิ่มเติม
หมายเหตุ : แทนที่จะใช้ <<:
แทรกฟิลด์เพื่อวางนิพจน์ผสาน ขณะนี้คุณสามารถใช้ <<<:
ได้เช่นกัน ซึ่งอนุญาตให้ใช้ตัวแยกวิเคราะห์ yaml ปกติสำหรับเอกสาร yaml ที่มีลักษณะคล้าย spiff <<:
ถูกเก็บไว้เพื่อความเข้ากันได้แบบย้อนหลัง ถูกนำมาใช้ ( (( merge required ))
)
หากไม่ควรตีความคีย์ผสานเป็นคีย์ปกติแทนที่จะเป็นคำสั่งผสาน คีย์ดังกล่าวสามารถหลีกได้ด้วยเครื่องหมายอัศเจรีย์ ( !
)
ตัวอย่างเช่น ปุ่มแผนที่ <<<!
จะส่งผลให้มีคีย์สตริง <<<
และ <<<!!
จะส่งผลให้มีคีย์สตริง <<<!
ค่า.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge ))
b : 3
c : 4
spiff merge template.yml values.yml
ผลตอบแทน:
foo :
a : 1
b : 2
c : 4
ค่า.yml
foo :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge ))
- 4
spiff merge template.yml values.yml
ผลตอบแทน:
foo :
- 3
- 1
- 2
- 4
- <<: (( merge on key ))
spiff
สามารถรวมรายการแผนที่เข้ากับฟิลด์คีย์ได้ รายการเหล่านั้นได้รับการจัดการเหมือนกับแผนที่ที่มีค่าของฟิลด์คีย์เป็นคีย์ ตามค่าเริ่มต้น name
คีย์จะถูกใช้ แต่ด้วยตัวเลือก on
ชื่อคีย์ที่กำหนดเองสามารถระบุสำหรับนิพจน์การรวมรายการได้
เช่น:
list :
- << : (( merge on key ))
- key : alice
age : 25
- key : bob
age : 24
รวมเข้ากับ
list :
- key : alice
age : 20
- key : peter
age : 13
อัตราผลตอบแทน
list :
- key : peter
age : 13
- key : alice
age : 20
- key : bob
age : 24
หากไม่ต้องการแทรกรายการใหม่ (ตามที่ร้องขอโดยนิพจน์การรวมการแทรก) แต่เพียงแทนที่รายการที่มีอยู่เท่านั้น ฟิลด์คีย์ที่มีอยู่หนึ่งฟิลด์สามารถขึ้นต้นด้วย key:
เพื่อระบุชื่อคีย์ที่ไม่เป็นมาตรฐาน เช่น - key:key: alice
<<: (( merge replace ))
แทนที่เนื้อหาทั้งหมดขององค์ประกอบด้วยเนื้อหาที่พบในต้นขั้วบางส่วน แทนที่จะทำการผสานเนื้อหาที่มีอยู่อย่างลึกซึ้ง
ค่า.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge replace ))
b : 3
c : 4
spiff merge template.yml values.yml
ผลตอบแทน:
foo :
a : 1
b : 2
ค่า.yml
foo :
- 1
- 2
template.yml
foo :
- << : (( merge replace ))
- 3
- 4
spiff merge template.yml values.yml
ผลตอบแทน:
foo :
- 1
- 2
<<: (( foo ))
การรวมแผนที่และรายการที่พบในเทมเพลตหรือต้นขั้วเดียวกัน
foo :
a : 1
b : 2
bar :
<< : (( foo )) # any dynaml expression
b : 3
อัตราผลตอบแทน:
foo :
a : 1
b : 2
bar :
a : 1
b : 3
นิพจน์นี้เพียงเพิ่มรายการใหม่ลงในรายการจริง โดยจะไม่รวมรายการที่มีอยู่เข้ากับเนื้อหาที่อธิบายโดยนิพจน์การผสาน
bar :
- 1
- 2
foo :
- 3
- << : (( bar ))
- 4
อัตราผลตอบแทน:
bar :
- 1
- 2
foo :
- 3
- 1
- 2
- 4
กรณีการใช้งานทั่วไปสำหรับสิ่งนี้คือการรวมรายการของ ips หรือช่วงคงที่เข้ากับรายการ ips ความเป็นไปได้อีกอย่างหนึ่งคือการใช้นิพจน์การต่อข้อมูลเดี่ยว
<<: (( merge foo ))
การรวมแผนที่หรือรายการเข้ากับเนื้อหาขององค์ประกอบที่กำหนดเองที่พบในต้นขั้วบางส่วน (การรวมการเปลี่ยนเส้นทาง) จะไม่มีการผสาน (เชิงลึก) อีกต่อไปกับองค์ประกอบที่มีชื่อเดียวกันที่พบในต้นขั้วบางส่วน (การผสานรายการแบบลึกต้องใช้แผนที่ที่มี name
ฟิลด์)
การเปลี่ยนเส้นทางผสานสามารถใช้เป็นค่าฟิลด์โดยตรงได้เช่นกัน พวกเขาสามารถใช้ร่วมกับการแทนที่การผสานเช่น (( merge replace foo ))
ค่า.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
foo :
<< : (( merge bar))
b : 3
c : 4
spiff merge template.yml values.yml
ผลตอบแทน:
foo :
a : 1
b : 2
c : 4
อีกวิธีในการผสานกับองค์ประกอบอื่นในบางต้นขั้วก็สามารถทำได้ด้วยวิธีดั้งเดิม:
ค่า.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
bar :
<< : (( merge ))
b : 3
c : 4
foo : (( bar ))
แต่ในสถานการณ์นี้ การผสานยังคงทำการผสานเชิงลึกกับชื่อองค์ประกอบดั้งเดิม ดังนั้น spiff merge template.yml values.yml
ให้ผลตอบแทน:
bar :
a : 1
b : 2
c : 4
foo :
a : 10
b : 20
c : 4
ค่า.yml
foo :
- 10
- 20
bar :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge bar ))
- 4
spiff merge template.yml values.yml
ผลตอบแทน:
foo :
- 3
- 1
- 2
- 4
<<: (( merge none ))
หากการอ้างอิงของการผสานการเปลี่ยนเส้นทางถูกตั้งค่าเป็นค่าคงที่ none
จะไม่มีการผสานเลย นิพจน์นี้ให้ค่าศูนย์เสมอ
เช่น: สำหรับ
template.yml
map :
<< : (( merge none ))
value : notmerged
ค่า.yml
map :
value : merged
spiff merge template.yml values.yml
ผลตอบแทน:
map :
value : notmerged
สามารถใช้สำหรับการรวมฟิลด์ที่ชัดเจนโดยใช้ฟังก์ชัน stub
เพื่อเข้าถึงส่วนเฉพาะของต้นขั้วต้นทาง
เช่น:
template.yml
map :
<< : (( merge none ))
value : (( "alice" "+" stub(map.value) ))
ค่า.yml
map :
value : bob
spiff merge template.yml values.yml
ผลตอบแทน:
test :
value : alice+bob
นอกจากนี้ยังใช้ได้กับสาขาเฉพาะ:
template.yml
map :
value : (( merge none // "alice" "+" stub() ))
ค่า.yml
map :
value : bob
spiff merge template.yml values.yml
ผลตอบแทน:
test :
value : alice+bob
(( a || b ))
ใช้ a หรือ b ถ้า a ไม่สามารถแก้ไขได้
เช่น:
foo :
bar :
- name : some
- name : complicated
- name : structure
mything :
complicated_structure : (( merge || foo.bar ))
การดำเนินการนี้จะพยายามผสานใน mything.complicated_structure
หรือหากไม่สามารถผสานได้ ให้ใช้ค่าเริ่มต้นที่ระบุใน foo.bar
ตัวดำเนินการ //
ตรวจสอบเพิ่มเติมว่า a
สามารถแก้ไขได้เป็นค่าที่ถูกต้องหรือไม่ (ไม่เท่ากับ ~
)
(( 1 + 2 * foo ))
นิพจน์ Dynaml สามารถใช้เพื่อดำเนินการคำนวณจำนวนเต็มทางคณิตศาสตร์และจำนวนจุดลอยตัวได้ การดำเนินการที่รองรับคือ +
, -
, *
และ /
ตัวดำเนินการแบบโมดูโล ( %
) รองรับเฉพาะตัวถูกดำเนินการจำนวนเต็มเท่านั้น
เช่น:
ค่า.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
ให้ผลตอบแทน 7
สำหรับ bar
สามารถใช้ร่วมกับการต่อข้อมูลได้ (การคำนวณมีลำดับความสำคัญสูงกว่าการต่อข้อมูลในนิพจน์ dynaml):
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
ผลลัพธ์คือสตริง 3 times 2 yields 6
(( "10.10.10.10" - 11 ))
นอกจากเลขคณิตของจำนวนเต็มแล้ว ยังสามารถใช้การบวกและการลบที่อยู่ IP และ Cidrs ได้อีกด้วย
เช่น:
ip : 10.10.10.10
range : (( ip "-" ip + 247 + 256 * 256 ))
อัตราผลตอบแทน
ip : 10.10.10.10
range : 10.10.10.10-10.11.11.1
การลบยังใช้ได้กับที่อยู่ IP สองรายการหรือ cidrs เพื่อคำนวณจำนวนที่อยู่ IP ระหว่างที่อยู่ IP สองแห่ง
เช่น:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
ให้ค่า 256 ค่าคงที่ที่อยู่ IP สามารถนำมาใช้โดยตรงในนิพจน์ dynaml พวกเขาจะถูกแปลงเป็นสตริงโดยปริยายและกลับไปเป็นที่อยู่ IP หากจำเป็นในการดำเนินการ
การคูณและการหารสามารถใช้เพื่อจัดการกับการเปลี่ยนแปลงช่วง IP บน CIDR ด้วยการแบ่งเครือข่ายสามารถแบ่งพาร์ติชันได้ ขนาดเครือข่ายเพิ่มขึ้นเพื่อให้มีซับเน็ตเฉพาะจำนวนต่ำกว่า CIDR ดั้งเดิมเป็นอย่างน้อย การคูณสามารถใช้เพื่อรับเครือข่ายย่อยถัดไปที่ n ที่มีขนาดเท่ากัน
เช่น:
subnet : (( "10.1.2.1/24" / 12 )) # first subnet CIDR for 16 subnets
next : (( "10.1.2.1/24" / 12 * 2)) # 2nd next (3rd) subnet CIDRS
อัตราผลตอบแทน
subnet : 10.1.2.0/28
next : 10.1.2.32/28
นอกจากนี้ยังมีฟังก์ชันที่ทำงานบน IPv4 CIDR:
cidr : 192.168.0.1/24
range : (( min_ip(cidr) "-" max_ip(cidr) ))
next : (( max_ip(cidr) + 1 ))
num : (( min_ip(cidr) "+" num_ip(cidr) "=" min_ip(cidr) + num_ip(cidr) ))
contains : (( contains_ip(cidr, "192.168.0.2") ))
อัตราผลตอบแทน
cidr : 192.168.0.1/24
range : 192.168.0.0-192.168.0.255
next : 192.168.1.0
num : 192.168.0.0+256=192.168.1.0
contains : true
(( a > 1 ? foo :bar ))
Dynaml รองรับตัวดำเนินการเปรียบเทียบ <
, <=
, ==
, !=
, >=
และ >
ตัวดำเนินการเปรียบเทียบทำงานกับค่าจำนวนเต็ม การตรวจสอบความเท่าเทียมกันยังใช้ได้กับรายการและแผนที่อีกด้วย ผลลัพธ์จะเป็นค่าบูลีนเสมอ หากต้องการลบล้างเงื่อนไข สามารถใช้ unary not opertor ( !
) ได้
นอกจากนี้ยังมีตัวดำเนินการแบบมีเงื่อนไขแบบไตรภาค ?:
ซึ่งสามารถใช้ในการประเมินนิพจน์โดยขึ้นอยู่กับเงื่อนไข ตัวถูกดำเนินการตัวแรกถูกใช้เป็นเงื่อนไข นิพจน์จะถูกประเมินเป็นตัวถูกดำเนินการตัวที่สอง หากเงื่อนไขเป็นจริง และตัวถูกดำเนินการตัวที่สาม มิฉะนั้น
เช่น:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
ให้ค่า bob
สำหรับ name
คุณสมบัติ
นิพจน์จะถือว่าเป็น false
หากประเมินเป็น
false
มิฉะนั้นจะถือว่าเป็น true
หมายเหตุ
การใช้สัญลักษณ์ :
อาจขัดแย้งกับไวยากรณ์ yaml หากนิพจน์ที่สมบูรณ์ไม่ใช่ค่าสตริงที่ยกมา
ตัวดำเนินการ -or
และ -and
สามารถใช้เพื่อรวมตัวดำเนินการเปรียบเทียบเพื่อสร้างเงื่อนไขที่ซับซ้อนมากขึ้น
หมายเหตุ:
สัญลักษณ์ตัวดำเนินการแบบดั้งเดิมมากขึ้น ||
(และ &&
) ไม่สามารถใช้ได้ที่นี่ เนื่องจากตัวดำเนินการ ||
มีอยู่แล้วใน dynaml ที่มีความหมายต่างกัน ซึ่งไม่ถือเป็นการดำเนินการเชิงตรรกะ นิพจน์ false || true
ประเมินเป็น false
เนื่องจากจะให้ผลตัวถูกดำเนินการตัวแรก หากมีการกำหนดไว้ โดยไม่คำนึงถึงค่าของมัน เพื่อให้เข้ากันได้มากที่สุดเท่าที่จะเป็นไปได้ สิ่งนี้ไม่สามารถเปลี่ยนแปลงได้ และสัญลักษณ์เปล่า or
และ and
ไม่สามารถนำมาใช้ได้ เนื่องจากจะทำให้การต่อข้อมูลอ้างอิงกับชื่อดังกล่าวเป็นโมฆะ
(( 5 -or 6 ))
หากทั้งสองด้านของตัวดำเนินการ -or
หรือ -and
และ ประเมินเป็นค่าจำนวนเต็ม การดำเนินการแบบบิตฉลาดจะถูกดำเนินการ และผลลัพธ์จะเป็นจำนวนเต็มอีกครั้ง ดังนั้นนิพจน์ 5 -or 6
จึงประเมินเป็น 7
Dynaml รองรับชุดฟังก์ชันที่กำหนดไว้ล่วงหน้า โดยทั่วไปฟังก์ชันจะเรียกว่า like
result : (( functionname(arg, arg, ...) ))
ฟังก์ชั่นเพิ่มเติมอาจถูกกำหนดให้เป็นส่วนหนึ่งของเอกสาร yaml โดยใช้นิพจน์แลมบ์ดา ชื่อฟังก์ชันอาจเป็นนิพจน์ที่จัดกลุ่มหรือเส้นทางไปยังโหนดที่โฮสต์นิพจน์แลมบ์ดา
(( format( "%s %d", alice, 25) ))
จัดรูปแบบสตริงตามอาร์กิวเมนต์ที่กำหนดโดยนิพจน์ dynaml ฟังก์ชันนี้มีรูปแบบที่สอง: error
จัดรูปแบบข้อความแสดงข้อผิดพลาดและตั้งค่าการประเมินให้ล้มเหลว
(( join( ", ", list) ))
รวมรายการของรายการหรือค่าโดยตรงกับค่าสตริงเดียวโดยใช้สตริงตัวคั่นที่กำหนด อาร์กิวเมนต์ที่จะรวมอาจเป็นนิพจน์ dynaml ที่ประเมินรายการ ซึ่งค่าอีกครั้งเป็นสตริงหรือจำนวนเต็ม หรือค่าสตริงหรือจำนวนเต็ม
เช่น:
alice : alice
list :
- foo
- bar
join : (( join(", ", "bob", list, alice, 10) ))
ให้ผลตอบแทนค่าสตริง bob, foo, bar, alice, 10
สำหรับ join
(( split( ",", string) ))
แยกสตริงสำหรับตัวคั่นเฉพาะ ผลลัพธ์ที่ได้คือรายการ แทนที่จะใช้สตริงตัวคั่น อาจกำหนดค่าจำนวนเต็ม ซึ่งจะแยกสตริงที่ให้ออกเป็นรายการสตริงที่จำกัดความยาว ความยาวนับเป็นอักษรรูน ไม่ใช่ไบต์
เช่น:
list : (( split("," "alice, bob") ))
limited : (( split(4, "1234567890") ))
อัตราผลตอบแทน:
list :
- alice
- ' bob '
limited :
- " 1234 "
- " 5678 "
- " 90 "
อาจระบุอาร์กิวเมนต์ที่ 3 เผื่อเลือกได้ มันจำกัดจำนวนรายการส่งคืน ค่า -1 นำไปสู่ความยาวรายการไม่จำกัด
หากควรใช้นิพจน์ทั่วไปเป็นสตริงตัวคั่น คุณสามารถใช้ฟังก์ชัน split_match
ได้
(( trim(string) ))
ตัดสตริงหรือองค์ประกอบทั้งหมดของรายการสตริง มีอาร์กิวเมนต์สตริงที่สองที่เป็นทางเลือก สามารถใช้เพื่อระบุชุดอักขระที่จะตัดได้ ชุดการตัดเริ่มต้นประกอบด้วยช่องว่างและอักขระแท็บ
เช่น:
list : (( trim(split("," "alice, bob")) ))
อัตราผลตอบแทน:
list :
- alice
- bob
(( element(list, index) ))
ส่งคืนองค์ประกอบรายการเฉพาะที่กำหนดโดยดัชนีของมัน
เช่น:
list : (( trim(split("," "alice, bob")) ))
elem : (( element(list,1) ))
อัตราผลตอบแทน:
list :
- alice
- bob
elem : bob
(( element(map, key) ))
ส่งคืนฟิลด์แผนที่เฉพาะที่กำหนดโดยคีย์
map :
alice : 24
bob : 25
elem : (( element(map,"bob") ))
อัตราผลตอบแทน:
map :
alice : 24
bob : 25
elem : 25
ฟังก์ชันนี้ยังสามารถรองรับคีย์ที่มีจุด (.)
(( compact(list) ))
กรองรายการโดยละเว้นรายการว่าง
เช่น:
list : (( compact(trim(split("," "alice, , bob"))) ))
อัตราผลตอบแทน:
list :
- alice
- bob
(( uniq(list) ))
Uniq จัดทำรายการที่ไม่มีรายการซ้ำ
เช่น:
list :
- a
- b
- a
- c
- a
- b
- 0
- " 0 "
uniq : (( uniq(list) ))
อัตราผลตอบแทนสำหรับฟิลด์ uniq
:
uniq :
- a
- b
- c
- 0
(( contains(list, "foobar") ))
ตรวจสอบว่ารายการมีค่าเฉพาะหรือไม่ ค่าอาจเป็นรายการหรือแผนที่ด้วย
เช่น:
list :
- foo
- bar
- foobar
contains : (( contains(list, "foobar") ))
อัตราผลตอบแทน:
list :
- foo
- bar
- foobar
contains : true
ฟังก์ชั่น contains
ยังใช้ได้กับสตริงเพื่อค้นหาสตริงย่อยหรือแมปเพื่อค้นหาคีย์ ในกรณีดังกล่าว องค์ประกอบจะต้องเป็นสตริง
เช่น:
contains : (( contains("foobar", "bar") ))
ให้ผล true
(( basename(path) ))
basename
ฟังก์ชันส่งกลับชื่อขององค์ประกอบสุดท้ายของเส้นทาง อาร์กิวเมนต์อาจเป็นชื่อเส้นทางปกติหรือ URL
เช่น:
pathbase : (( basename("alice/bob") ))
urlbase : (( basename("http://foobar/alice/bob?any=parameter") ))
อัตราผลตอบแทน:
pathbase : bob
urlbase : bob
(( dirname(path) ))
dirname
ฟังก์ชันส่งคืนไดเร็กทอรีหลักของเส้นทาง อาร์กิวเมนต์อาจเป็นชื่อเส้นทางปกติหรือ URL
เช่น:
pathbase : (( dirname("alice/bob") ))
urlbase : (( dirname("http://foobar/alice/bob?any=parameter") ))
อัตราผลตอบแทน:
pathbase : alice
urlbase : /alice
(( parseurl("http://github.com") ))
ฟังก์ชันนี้แยกวิเคราะห์ URL และสร้างแผนที่พร้อมองค์ประกอบทั้งหมดของ URL port
ฟิลด์ userinfo
และ password
เป็นทางเลือก
เช่น:
url : (( parseurl("https://user:[email protected]:443/mandelsoft/spiff?branch=master&tag=v1#anchor") ))
อัตราผลตอบแทน:
url :
scheme : https
host : github.com
port : 443
path : /mandelsoft/spiff
fragment : anchor
query : branch=master&tag=v1
values :
branch : [ master ]
tag : [ v1 ]
userinfo :
username : user
password : pass
(( index(list, "foobar") ))
ตรวจสอบว่ารายการมีค่าเฉพาะหรือไม่และส่งคืนดัชนีของนัดแรกหรือไม่ ค่าอาจเป็นรายการหรือแผนที่ หากไม่พบรายการ -1
จะถูกส่งคืน
เช่น:
list :
- foo
- bar
- foobar
index : (( index(list, "foobar") ))
อัตราผลตอบแทน:
list :
- foo
- bar
- foobar
index : 2
index
ฟังก์ชั่นยังใช้งานได้กับสตริงเพื่อค้นหาสตริงย่อย
เช่น:
index : (( index("foobar", "bar") ))
ให้ผลตอบแทน 3
.
(( lastindex(list, "foobar") ))
ฟังก์ชั่น lastindex
ทำงานเหมือน index
แต่ดัชนีของการเกิดขึ้นครั้งสุดท้ายจะถูกส่งคืน
sort
ฟังก์ชั่นสามารถใช้ในการเรียงลำดับจำนวนเต็มหรือรายการสตริง การดำเนินการเรียงลำดับมีความเสถียร
เช่น:
list :
- alice
- foobar
- bob
sorted : (( sort(list) ))
อัตราผลตอบแทนสำหรับ sorted
- alice
- bob
- foobar
หากควรเรียงลำดับประเภทอื่น ๆ โดยเฉพาะอย่างยิ่งประเภทที่ซับซ้อนเช่นรายการหรือแผนที่หรือกฎการเปรียบเทียบที่แตกต่างกันนั้นจำเป็นต้องระบุฟังก์ชั่นการเปรียบเทียบเป็นอาร์กิวเมนต์ตัวเลือกที่สอง ฟังก์ชั่นการเปรียบเทียบจะต้องเป็นนิพจน์แลมบ์ดาโดยใช้สองข้อโต้แย้ง ประเภทผลลัพธ์จะต้องเป็น integer
หรือ bool
ระบุว่า A นั้นน้อยกว่าแล้ว b หากจำนวนเต็มถูกส่งคืนควรเป็น
เช่น:
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
อัตราผลตอบแทนสำหรับ sorted
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
แทนที่การเกิดขึ้นทั้งหมดของสตริงย่อยในสตริงโดยสตริงการเปลี่ยน ด้วยอาร์กิวเมนต์จำนวนเต็มที่สี่เป็นตัวเลือกจำนวนการแทนที่อาจมี จำกัด (-1 หมายถึงไม่ จำกัด )
เช่น:
string : (( replace("foobar", "o", "u") ))
ให้ผลผลิต fuubar
หากควรใช้นิพจน์ทั่วไปเป็นสตริงการค้นหาสามารถใช้ฟังก์ชั่น replace_match
ได้ นี่คือการประเมินสตริงการค้นหาเป็นนิพจน์ทั่วไป มันอาจจะเป็นนิพจน์ย่อย การจับคู่เหล่านี้สามารถใช้ในสตริงการเปลี่ยน
เช่น:
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
ให้ผลผลิต fbooar
อาร์กิวเมนต์ทดแทนอาจเป็นฟังก์ชันแลมบ์ดา ในกรณีนี้สำหรับทุกการแข่งขันฟังก์ชั่นจะถูกเรียกเพื่อกำหนดค่าการเปลี่ยน อาร์กิวเมนต์อินพุตเดียวคือรายการของนิพจน์ย่อยที่ตรงกับการจับคู่
เช่น:
string : (( replace_match("foobar-barfoo", "(o*)b", |m|->upper(m.[1]) "b" ) ))
ให้ผลผลิต fOObar-barfoo
(( substr(string, 1, 2) ))
แยกสตริงต้นขั้วจากสตริงเริ่มต้นจากดัชนีเริ่มต้นที่กำหนดจนถึงดัชนีปลายทางเสริม (พิเศษ) หากไม่มีดัชนีสิ้นสุดจะได้รับสตรุฟย่อยจนถึงตอนท้ายของสตริงจะถูกสกัด ดัชนีทั้งสองอาจเป็นลบ ในกรณีนี้พวกเขาถูกนำมาจากปลายสตริง
เช่น:
string : " foobar "
end1 : (( substr(string,-2) ))
end2 : (( substr(string,3) ))
range : (( substr(string,1,-1) ))
ประเมิน
string : foobar
end1 : ar
end2 : bar
range : ooba
(( match("(f.*)(b.*)", "xxxfoobar") ))
ส่งคืนการจับคู่ของนิพจน์ทั่วไปสำหรับค่าสตริงที่กำหนด การแข่งขันเป็นรายการของค่าที่ตรงกันสำหรับนิพจน์ย่อยที่มีอยู่ในนิพจน์ทั่วไป ดัชนี 0 หมายถึงการจับคู่ของนิพจน์ทั่วไปที่สมบูรณ์ หากค่าสตริงไม่ตรงกับรายการว่างจะถูกส่งคืน
เช่น:
matches : (( match("(f.*)*(b.*)", "xxxfoobar") ))
อัตราผลตอบแทน:
matches :
- foobar
- foo
- bar
อาร์กิวเมนต์ที่สามของประเภทจำนวนเต็มอาจได้รับการร้องขอการจับคู่หลายครั้งสูงสุดของการทำซ้ำ N หากค่าเป็นลบการทำซ้ำทั้งหมดจะถูกรายงาน ผลลัพธ์คือรายการของการแข่งขันทั้งหมดแต่ละรายการในรูปแบบที่อธิบายไว้ข้างต้น
(( keys(map) ))
กำหนดรายการที่เรียงลำดับของคีย์ที่ใช้ในแผนที่
เช่น:
map :
alice : 25
bob : 25
keys : (( keys(map) ))
อัตราผลตอบแทน:
map :
alice : 25
bob : 25
keys :
- alice
- bob
(( length(list) ))
กำหนดความยาวของรายการแผนที่หรือค่าสตริง
เช่น:
list :
- alice
- bob
length : (( length(list) ))
อัตราผลตอบแทน:
list :
- alice
- bob
length : 2
(( base64(string) ))
ฟังก์ชั่น base64
สร้างการเข้ารหัส BASE64 ของสตริงที่กำหนด base64_decode
ถอดรหัสสตริงที่เข้ารหัส Base64
เช่น:
base64 : (( base64("test") ))
test : (( base64_decode(base64)))
ประเมิน
base54 : dGVzdA==
test : test
อาร์กิวเมนต์ตัวเลือกที่สองสามารถใช้เพื่อระบุความยาวของเส้นสูงสุด ในกรณีนี้ผลลัพธ์จะเป็นสตริงหลายบรรทัด
(( hash(string) ))
ฟังก์ชั่น hash
สร้างแฮชหลายชนิดสำหรับสตริงที่กำหนด โดยค่าเริ่มต้นเป็นแฮช sha256
ถูกสร้างขึ้น อาร์กิวเมนต์ตัวเลือกที่สองระบุประเภทแฮช ประเภทที่เป็นไปได้คือ md4
, md5
, sha1
, sha224
, sha256
, sha384
, sha2512
, sha512/224
หรือ sha512/256
md5
แฮชยังสามารถสร้างได้โดย Finctio md5(string)
ที่เลิกใช้แล้ว
เช่น:
data : alice
hash :
deprecated : (( md5(data) ))
md4 : (( hash(data,"md4") ))
md5 : (( hash(data,"md5") ))
sha1 : (( hash(data,"sha1") ))
sha224 : (( hash(data,"sha224") ))
sha256 : (( hash(data,"sha256") ))
sha384 : (( hash(data,"sha384") ))
sha512 : (( hash(data,"sha512") ))
sha512_224 : (( hash(data,"sha512/224") ))
sha512_256 : (( hash(data,"sha512/256") ))
ประเมิน
data : alice
hash :
deprecated : 6384e2b2184bcbf58eccf10ca7a6563c
md4 : 616c69636531d6cfe0d16ae931b73c59d7e0c089c0
md5 : 6384e2b2184bcbf58eccf10ca7a6563c
sha1 : 522b276a356bdf39013dfabea2cd43e141ecc9e8
sha224 : 38b7e5d5651aaf85694a7a7c6d5db1275af86a6df93a36b8a4a2e771
sha256 : 2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90
sha384 : 96a5353e625adc003a01bdcd9b21b21189bdd9806851829f45b81d3dfc6721ee21f6e0e98c4dd63bc559f66c7a74233a
sha512 : 408b27d3097eea5a46bf2ab6433a7234a33d5e49957b13ec7acc2ca08e1a13c75272c90c8d3385d47ede5420a7a9623aad817d9f8a70bd100a0acea7400daa59
sha512_224 : c3b8cfaa37ae15922adf3d21606e3a9836ba2a9d7838b040b7c96fd7
sha512_256 : ad0a339b08dc090fe3b16eae376f7e162836e8728da9c45466842e19508d7627
(( bcrypt("password", 10) ))
ฟังก์ชั่น bcrypt
สร้างแฮชรหัสผ่าน bcrypt สำหรับสตริงที่กำหนดโดยใช้ปัจจัยต้นทุนที่ระบุ (เริ่มต้นเป็น 10 ถ้าหายไป)
เช่น:
hash : (( bcrypt("password", 10) ))
ประเมิน
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
(( bcrypt_check("password", hash) ))
ฟังก์ชั่น bcrypt_check
ตรวจสอบรหัสผ่านกับแฮช bcrypt ที่กำหนด
เช่น:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
ประเมิน
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : true
(( md5crypt("password") ))
ฟังก์ชั่น md5crypt
สร้างแฮชรหัสผ่าน Apache MD5 ที่เข้ารหัสสำหรับสตริงที่กำหนด
เช่น:
hash : (( md5crypt("password") ))
ประเมิน
hash : $apr1$3Qc1aanY$16Sb5h7U1QrcqwZbDJIYZ0
(( md5crypt_check("password", hash) ))
ฟังก์ชั่น md5crypt_check
ตรวจสอบรหัสผ่านกับแฮชที่เข้ารหัส Apache MD5 ที่กำหนด
เช่น:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
ประเมิน
hash : $apr1$B77VuUUZ$NkNFhkvXHW8wERSRoi74O1
valid : true
(( decrypt("secret") ))
ฟังก์ชั่นนี้สามารถใช้ในการจัดเก็บความลับที่เข้ารหัสในไฟล์ Spiff Yaml ผลลัพธ์ที่ผ่านการประมวลผลจะมีค่าที่ถอดรหัส ทุกประเภทโหนดสามารถเข้ารหัสและถอดรหัสได้รวมถึงแผนที่และรายการที่สมบูรณ์
รหัสผ่านสำหรับการถอดรหัสสามารถกำหนดเป็นอาร์กิวเมนต์ที่สองหรือ (วิธีที่ต้องการ) สามารถระบุได้โดยตัวแปรสภาพแวดล้อม SPIFF_ENCRYPTION_KEY
อาร์กิวเมนต์สุดท้ายที่เป็นตัวเลือกอาจเลือกวิธีการเข้ารหัส วิธีเดียวที่รองรับจนถึงตอนนี้คือ 3DES
อาจมีการเพิ่มวิธีการอื่น ๆ สำหรับรุ่น Spiff โดยเฉพาะโดยใช้การลงทะเบียนวิธีการเข้ารหัสที่เสนอโดย SPIFF Library
สามารถเข้ารหัสค่าได้โดยใช้ฟังก์ชั่น encrypt("secret")
เช่น:
password : this a very secret secret and may never be exposed to unauthorized people
encrypted : (( encrypt("spiff is a cool tool", password) ))
decrypted : (( decrypt(encrypted, password) ))
ประเมินเป็นสิ่งที่ชอบ
decrypted : spiff is a cool tool
encrypted : d889f9e4cc7ae13effcbc8bb8cd0c38d1fb2197738444f753c48796d7946083e6639e5a1bf8f77648f2a1ddf37023c65ff57d52d0519d1d92cbcf87d3e263cba
password : this a very secret secret and may never be exposed to unauthorized people
(( rand("[:alnum:]", 10) ))
ฟังก์ชั่น rand
สร้างค่าสุ่ม อาร์กิวเมนต์แรกตัดสินใจว่ามีการร้องขอค่าใด ไม่มีการโต้แย้งมันจะสร้างตัวเลขสุ่มบวกในช่วง int64
ประเภทอาร์กิวเมนต์ | ผลลัพธ์ |
---|---|
ภายใน | ค่าจำนวนเต็มในช่วง [0, n ) สำหรับบวก n และ ( n , 0] สำหรับลบ n |
บูล | ค่าบูลีน |
เชือก | หนึ่งสตริงรูนที่รูรินอยู่ในช่วงอักขระที่กำหนดการรวมกันของคลาสอักขระหรือช่วงอักขระที่ใช้งานได้สำหรับ Regexp สามารถใช้งานได้ หากมีการระบุอาร์กิวเมนต์ความยาวเพิ่มเติมสตริงผลลัพธ์จะมีความยาวที่กำหนด |
เช่น:
int : (( rand() ))
int10 : (( rand(10) ))
neg10 : (( rand(-10) ))
bool : (( rand(true) ))
string : (( rand("[:alpha:][:digit:]-", 10) ))
upper : (( rand("A-Z", 10) ))
punct : (( rand("[:punct:]", 10) ))
alnum : (( rand("[:alnum:]", 10) ))
ประเมิน
int : 8037669378456096839
int10 : 7
neg10 : -5
bool : true
string : ghhjAYPMlD
upper : LBZQFRSURL
alnum : 0p6KS7EhAj
punct : ' &{;,^])"(# '
(( type(foobar) ))
type
ฟังก์ชั่นให้สตริงที่แสดงถึงประเภทของนิพจน์ที่กำหนด
เช่น:
template :
<< : (( &template ))
types :
- int : (( type(1) ))
- float : (( type(1.0) ))
- bool : (( type(true) ))
- string : (( type("foobar") ))
- list : (( type([]) ))
- map : (( type({}) ))
- lambda : (( type(|x|->x) ))
- template : (( type(.template) ))
- nil : (( type(~) ))
- undef : (( type(~~) ))
ประเมินประเภทไป
types :
- int : int
- float : float
- bool : bool
- string : string
- list : list
- map : map
- lambda : lambda
- template : template
(( defined(foobar) ))
ฟังก์ชั่น defined
ตรวจสอบว่าสามารถประเมินนิพจน์ได้สำเร็จหรือไม่ มันให้ค่าบูลีน true
หากสามารถประเมินนิพจน์และ false
เป็นอย่างอื่น
เช่น:
zero : 0
div_ok : (( defined(1 / zero ) ))
zero_def : (( defined( zero ) ))
null_def : (( defined( null ) ))
ประเมิน
zero : 0
div_ok : false
zero_def : true
null_def : false
ฟังก์ชั่นนี้สามารถใช้ร่วมกันของตัวดำเนินการแบบมีเงื่อนไขเพื่อประเมินนิพจน์ขึ้นอยู่กับความสามารถในการแก้ไขของนิพจน์อื่น
(( valid(foobar) ))
ฟังก์ชั่น valid
ตรวจสอบว่านิพจน์สามารถประเมินและประเมินผลเป็นค่าที่กำหนดไว้ได้สำเร็จไม่เท่ากับ nil
มันให้ค่าบูลีน true
หากสามารถประเมินนิพจน์และ false
เป็นอย่างอื่น
เช่น:
zero : 0
empty :
map : {}
list : []
div_ok : (( valid(1 / zero ) ))
zero_def : (( valid( zero ) ))
null_def : (( valid( ~ ) ))
empty_def : (( valid( empty ) ))
map_def : (( valid( map ) ))
list_def : (( valid( list ) ))
ประเมิน
zero : 0
empty : null
map : {}
list : []
div_ok : false
zero_def : true
null_def : false
empty_def : false
map_def : true
list_def : true
(( require(foobar) ))
ฟังก์ชั่น require
มีข้อผิดพลาดหากอาร์กิวเมนต์ที่กำหนดไม่ได้กำหนดหรือ nil
ฉะนั้นจะให้ค่าที่กำหนด
เช่น:
foo : ~
bob : (( foo || "default" ))
alice : (( require(foo) || "default" ))
ประเมิน
foo : ~
bob : ~
alice : default
(( stub(foo.bar) ))
stub
ฟังก์ชั่นให้ค่าของฟิลด์เฉพาะที่พบในต้นขั้วต้นน้ำแรกที่กำหนด
เช่น:
เทมเพลต
value : (( stub(foo.bar) ))
ผสานกับต้นขั้ว
stub.yml
foo :
bar : foobar
ประเมิน
value : foobar
อาร์กิวเมนต์ที่ส่งผ่านไปยังฟังก์ชั่นนี้จะต้องเป็นตัวอักษรอ้างอิงหรือนิพจน์ที่ประเมินไปยังสตริงที่แสดงถึงการอ้างอิงหรือรายการสตริงที่แสดงถึงรายการองค์ประกอบพา ธ สำหรับการอ้างอิง หากไม่มีการโต้แย้งหรือไม่ได้กำหนด ( ~~
) จะใช้เส้นทางฟิลด์จริง
โปรดทราบว่าการอ้างอิงเพียงอย่างเดียวที่กำหนดจะไม่ได้รับการประเมินเป็นนิพจน์หากควรใช้ค่าของมันจะต้องเปลี่ยนเป็นนิพจน์เช่นโดยการแสดง (ref)
หรือ [] ref
สำหรับนิพจน์รายการ
อีกทางเลือกหนึ่งการดำเนินการ merge
สามารถใช้เช่น merge foo.bar
ความแตกต่างคือ stub
ไม่รวมดังนั้นฟิลด์จะยังคงถูกรวมเข้าด้วยกัน (กับเส้นทางดั้งเดิมในเอกสาร)
(( tagdef("tag", value) ))
ฟังก์ชั่น tagdef
สามารถใช้เพื่อกำหนดแท็กแบบไดนามิก (ดูแท็ก) ตรงกันข้ามกับแท็กมาร์กเกอร์ฟังก์ชั่นนี้อนุญาตให้ระบุชื่อแท็กและค่าที่ต้องการโดยนิพจน์ ดังนั้นจึงสามารถใช้ในการเขียนองค์ประกอบเช่น map
หรือ sum
เพื่อสร้างแท็กแบบไดนามิกด้วยค่าที่คำนวณได้
อาร์กิวเมนต์ตัวเลือกที่สามสามารถใช้เพื่อระบุขอบเขตที่ต้องการ ( local
หรือ global
) โดยค่าเริ่มต้นแท็กโลคัลถูกสร้างขึ้น แท็กท้องถิ่นสามารถมองเห็นได้เฉพาะในระดับการประมวลผลจริง (เทมเพลตหรือย่อย) ในขณะที่แท็กทั่วโลกเมื่อกำหนดเมื่อกำหนดสามารถใช้ในทุกระดับการประมวลผลเพิ่มเติม (ต้นขั้วหรือเทมเพลต)
อีกทางเลือกหนึ่งชื่อแท็กสามารถนำหน้าด้วยการเริ่มต้น ( *
) เพื่อประกาศแท็กทั่วโลก
ค่าแท็กที่ระบุจะใช้เป็นผลลัพธ์สำหรับฟังก์ชัน
เช่น:
เทมเพลต
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
ประเมิน
value : 25
alice : 25
(( eval(foo "." bar ) ))
ประเมินผลการประเมินผลของการแสดงออกของสตริงอีกครั้งเป็นนิพจน์ DynamL ตัวอย่างเช่นนี้สามารถใช้เพื่อตระหนักถึงการยืนยัน
เช่น: การแสดงออกใน
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
คำนวณเส้นทางไปยังฟิลด์ซึ่งจะถูกประเมินอีกครั้งเพื่อให้ได้ค่าของฟิลด์ที่แต่งขึ้นนี้:
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
อ่านค่าของตัวแปรสภาพแวดล้อมที่มีชื่อเป็นนิพจน์ DynamL หากตัวแปรสภาพแวดล้อมไม่ได้ตั้งค่าการประเมินล้มเหลว
ในรสชาติที่สองฟังก์ชั่น env
ยอมรับอาร์กิวเมนต์และ/หรืออาร์กิวเมนต์รายการหลายรายการซึ่งเข้าร่วมกับรายการเดียว ทุกรายการในรายการนี้ใช้เป็นชื่อของตัวแปรสภาพแวดล้อมและผลลัพธ์ของฟังก์ชั่นคือแผนที่ของตัวแปรที่กำหนดเป็นองค์ประกอบ YAML ตัวแปรสภาพแวดล้อมที่ไม่มีอยู่จะถูกละเว้น
(( parse(yamlorjson) ))
แยกวิเคราะห์สตริง Yaml หรือ JSON และส่งคืนเนื้อหาเป็นค่า YAML ดังนั้นจึงสามารถใช้สำหรับการประเมินแบบไดนามิกเพิ่มเติม
เช่น:
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
ให้ผลตอบแทน 25
สำหรับ result
ของฟิลด์
ฟังก์ชั่น parse
รองรับอาร์กิวเมนต์ที่สองที่เป็นตัวเลือก โหมดแยกวิเคราะห์ ที่นี่โหมดเดียวกันนี้เป็นไปได้สำหรับฟังก์ชั่นการอ่าน โหมดการแยกวิเคราะห์เริ่มต้นคือ import
เนื้อหาเป็นเพียงการแยกวิเคราะห์และไม่มีการประเมินเพิ่มเติมในระหว่างขั้นตอนนี้
(( asjson(expr) ))
ฟังก์ชั่นนี้แปลงค่า YAML ที่กำหนดโดยอาร์กิวเมนต์เป็นสตริง JSON ฟังก์ชั่นที่สอดคล้องกัน asyaml
ให้ค่า YAML เป็นสตริง เอกสาร YAML
เช่น:
data :
alice : 25
mapped :
json : (( asjson(.data) ))
yaml : (( asyaml(.data) ))
แก้ไข
data :
alice : 25
mapped :
json : ' {"alice":25} '
yaml : |+
alice: 25
(( catch(expr) ))
ฟังก์ชั่นนี้ดำเนินการนิพจน์และให้ข้อมูลแผนที่ข้อมูลการประเมินบางอย่าง มันประสบความสำเร็จเสมอแม้ว่านิพจน์จะล้มเหลว แผนที่มีฟิลด์ต่อไปนี้:
ชื่อ | พิมพ์ | ความหมาย |
---|---|---|
valid | บูล | การแสดงออกนั้นถูกต้อง |
error | เชือก | ข้อความแสดงข้อผิดพลาดของการประเมินผล |
value | ใดๆ | ค่าของการแสดงออกหากการประเมินประสบความสำเร็จ |
เช่น:
data :
fail : (( catch(1 / 0) ))
valid : (( catch( 5 * 5) ))
แก้ไข
data :
fail :
error : division by zero
valid : false
valid :
error : " "
valid : true
value : 25
(( static_ips(0, 1, 3) ))
สร้างรายการ IPS แบบคงที่สำหรับงาน
เช่น:
jobs :
- name : myjob
instances : 2
networks :
- name : mynetwork
static_ips : (( static_ips(0, 3, 4) ))
สิ่งนี้จะสร้าง 3 IPS จากซับเน็ต m mynetwork
และส่งคืนสองรายการเนื่องจากมีเพียงสองอินสแตนซ์ ทั้งสองรายการจะเป็นออฟเซ็ตที่ 0 และ 3 จากช่วง IP แบบคงที่ที่กำหนดโดยเครือข่าย
ตัวอย่างเช่นให้ไฟล์ bye.yml :
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
และไฟล์ hi.yml :
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
spiff merge bye.yml hi.yml
ผลตอบแทน
jobs :
- instances : 3
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
- 10.60.3.70
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
-
ถ้า bye.yml แทน
networks : (( merge ))
jobs :
- name : myjob
instances : 2
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
spiff merge bye.yml hi.yml
แทนที่จะส่งคืน
jobs :
- instances : 2
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
static_ips
ยังยอมรับอาร์กิวเมนต์ของรายการตราบใดที่องค์ประกอบที่มีการถ่ายทอดทั้งหมดเป็นรายการอีกครั้งหรือค่าจำนวนเต็ม สิ่งนี้ช่วยให้ตัวย่อรายการ IPS ดังนี้:
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
ในขณะที่ฟังก์ชั่น static_ips ด้วยเหตุผลทางประวัติศาสตร์ขึ้นอยู่กับโครงสร้างของรายการ bosh และทำงานเฉพาะในสถานที่เฉพาะในรายการฟังก์ชัน ipset เสนอการคำนวณที่คล้ายกันอย่างหมดจดตามข้อโต้แย้งของมัน ดังนั้นช่วง IP ที่มีอยู่และจำนวน IP ที่ต้องการจะถูกส่งผ่านเป็นอาร์กิวเมนต์
อาร์กิวเมนต์แรก (ช่วง) สามารถเป็นช่วงเดียวเป็นสตริงง่าย ๆ หรือรายการสตริง ทุกสตริงอาจเป็น
อาร์กิวเมนต์ที่สองระบุจำนวนที่อยู่ IP ที่ร้องขอในชุดผลลัพธ์
อาร์กิวเมนต์เพิ่มเติมระบุดัชนีของ IPS ที่จะเลือก (เริ่มต้นจาก 0) ในช่วงที่กำหนด อาจใช้รายการดัชนีอีกครั้ง
เช่น:
ranges :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0/24
ipset : (( ipset(ranges,3,[256..260]) ))
แก้ไข ipset เป็น [ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]
หากไม่มีการระบุดัชนี IP (มีเพียงสองอาร์กิวเมนต์) IPS จะถูกเลือกเริ่มต้นจากจุดเริ่มต้นของช่วงแรกจนถึงจุดสิ้นสุดของช่วงสุดท้ายที่กำหนดโดยไม่มีทางอ้อม
(( list_to_map(list, "key") ))
รายการรายการแผนที่ที่มีฟิลด์ชื่อ/คีย์ที่ชัดเจนจะถูกแมปกับแผนที่ด้วยคีย์เฉพาะ โดยค่าเริ่มต้น name
ฟิลด์คีย์จะถูกใช้ซึ่งสามารถเปลี่ยนแปลงได้โดยอาร์กิวเมนต์ที่สองเลือก ฟิลด์คีย์ที่แสดงอย่างชัดเจนในรายการจะถูกนำมาพิจารณาด้วย
เช่น:
list :
- key:foo : alice
age : 24
- foo : bob
age : 30
map : (( list_to_map(list) ))
จะถูกแมปกับ
list :
- foo : alice
age : 24
- foo : bob
age : 30
map :
alice :
age : 24
bob :
age : 30
เมื่อใช้ร่วมกับเทมเพลตและการแสดงออกของแลมบ์ดาสิ่งนี้สามารถใช้ในการสร้างแผนที่ด้วยค่าคีย์ที่มีชื่อตามอำเภอใจแม้ว่าจะไม่ได้รับอนุญาตจากการแสดงออกของ DynamL สำหรับค่าคีย์
(( makemap(fieldlist) ))
ในรสชาตินี้ makemap
สร้างแผนที่พร้อมรายการที่อธิบายโดยรายการฟิลด์ที่กำหนด รายการคาดว่าจะมีแผนที่พร้อม key
รายการและ value
อธิบายรายการแผนที่เฉพาะ
เช่น:
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map : (( makemap(list) ))
อัตราผลตอบแทน
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map :
" 5 " : 25
alice : 24
bob : 25
หากค่าคีย์เป็นบูลีนหรือจำนวนเต็มมันจะถูกแมปกับสตริง
(( makemap(key, value) ))
ในรสชาตินี้ makemap
สร้างแผนที่ด้วยรายการที่อธิบายโดยคู่อาร์กิวเมนต์ที่กำหนด อาร์กิวเมนต์อาจเป็นลำดับของคู่คีย์/ค่า (กำหนดโดยอาร์กิวเมนต์แยกต่างหาก)
เช่น:
map : (( makemap("peter", 23, "paul", 22) ))
อัตราผลตอบแทน
map :
paul : 22
peter : 23
ตรงกันข้ามกับรสชาติ makemap
ก่อนหน้านี้อันนี้สามารถจัดการได้โดยตัวอักษรแผนที่
(( merge(map1, map2) ))
นอกเหนือจากคำหลัก merge
แล้วยังมีฟังก์ชั่นที่เรียกว่า merge
(จะต้องตามด้วยวงเล็บเปิดเสมอ) มันสามารถใช้ในการรวมแผนที่ severals ที่นำมาจากเอกสารจริงที่คล้ายคลึงกับกระบวนการผสานต้นขั้ว หากแผนที่ถูกระบุโดยนิพจน์อ้างอิงพวกเขาจะไม่สามารถมีการแสดงออก ของ DynamL ใด ๆ เนื่องจากพวกเขาจะถูกประเมินในบริบทของเอกสารจริงก่อนที่จะประเมินอาร์กิวเมนต์
เช่น:
map1 :
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
แก้ไข result
ที่
result :
alice : 26
bob : 24 # <---- expression evaluated before mergeing
เทมเพลตแผนที่หรือสามารถส่งผ่าน (โดยไม่มีผู้ดำเนินการประเมินผล!) ในกรณีนี้การแสดงออก ของ DynamL จากเทมเพลตจะได้รับการประเมินในขณะที่รวมเอกสารที่กำหนดไว้สำหรับการเรียกใช้ SPIFF Merge ปกติ
เช่น:
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
แก้ไข result
ที่
result :
alice : 26
bob : 26
แผนที่อาจได้รับจากนิพจน์แผนที่ ที่นี่เป็นไปได้ที่จะระบุการแสดงออกของ DynamL โดยใช้ไวยากรณ์ปกติ:
เช่น:
map1 :
alice : 24
bob : 25
map2 :
alice : 26
peter : 8
result : (( merge(map1, map2, { "bob"="(( carl ))", "carl"=100 }) ))
แก้ไข result
ที่
result :
alice : 26
bob : 100
แทนที่จะเป็นอาร์กิวเมนต์หลายรายการสามารถให้อาร์กิวเมนต์รายการเดียวได้ รายการจะต้องมีแผนที่ที่จะรวม
การผสานซ้อนกันสามารถเข้าถึงการผูกภายนอกทั้งหมด การอ้างอิงแบบสัมพัทธ์จะถูกค้นหาครั้งแรกในเอกสารจริง หากไม่พบว่ามีการผูกภายนอกทั้งหมดจะใช้ในการค้นหาข้อมูลอ้างอิงจากการผูกภายในถึงด้านนอก นอกจากนี้บริบท ( __ctx
) ยังมีสนาม OUTER
ซึ่งเป็นรายการของเอกสารภายนอกทั้งหมดของการผสานซ้อนกันซึ่งสามารถใช้ในการค้นหาการอ้างอิงสัมบูรณ์
เช่น:
data :
alice :
age : 24
template :
<< : (( &template ))
bob : 25
outer1 : (( __ctx.OUTER.[0].data )) # absolute access to outer context
outer2 : (( data.alice.age )) # relative access to outer binding
sum : (( .bob + .outer2 ))
merged : (( merge(template) ))
แก้ไข merged
เป็น
merged :
bob : 25
outer1 :
alice :
age : 24
outer2 : 24
sum : 49
(( intersect(list1, list2) ))
ฟังก์ชั่น intersect
ตัดกันหลายรายการ รายการอาจมีรายการทุกประเภท
เช่น:
list1 :
- - a
- - b
- a
- b
- { a: b }
- { b: c }
- 0
- 1
- " 0 "
- " 1 "
list2 :
- - a
- - c
- a
- c
- { a: b }
- { b: b }
- 0
- 2
- " 0 "
- " 2 "
intersect : (( intersect(list1, list2) ))
แก้ไข intersect
intersect :
- - a
- a
- { a: b }
- 0
- " 0 "
(( reverse(list) ))
ฟังก์ชั่น reverse
กลับลำดับของรายการ รายการอาจมีรายการทุกประเภท
เช่น:
list :
- - a
- b
- { a: b }
- { b: c }
- 0
- 1
reverse : (( reverse(list) ))
แก้ไข reverse
ไปที่
reverse :
- 1
- 0
- { b: c }
- { a: b }
- b
- - a
(( validate(value,"dnsdomain") ))
ฟังก์ validate
ความถูกต้องตรวจสอบการแสดงออกโดยใช้ชุดของตัวตรวจสอบ อาร์กิวเมนต์แรกคือค่าในการตรวจสอบและอาร์กิวเมนต์อื่น ๆ ทั้งหมดเป็นตัวตรวจสอบความถูกต้องที่ต้องประสบความสำเร็จในการยอมรับค่า หากการตรวจสอบอย่างน้อยหนึ่งรายการล้มเหลวข้อความแสดงข้อผิดพลาดที่เหมาะสมจะถูกสร้างขึ้นเพื่ออธิบายเหตุผลที่ล้มเหลว
ตัวตรวจสอบจะแสดงด้วยสตริงหรือรายการที่มีประเภทตัวตรวจสอบเป็นสตริงและอาร์กิวเมนต์ของมัน ผู้ตรวจสอบความถูกต้องสามารถคัดค้านด้วยการ preceeding !
ในชื่อของมัน
มีตัวตรวจสอบตัวต่อไปนี้:
พิมพ์ | ข้อโต้แย้ง | ความหมาย |
---|---|---|
empty | ไม่มี | รายการว่างเปล่าแผนที่หรือสตริง |
dnsdomain | ไม่มี | ชื่อโดเมน DNS |
wildcarddnsdomain | ไม่มี | ชื่อโดเมน Wildcard DNS |
dnslabel | ไม่มี | ฉลาก DNS |
dnsname | ไม่มี | โดเมน DNS หรือโดเมน Wildcard |
ip | ไม่มี | ที่อยู่ IP |
cidr | ไม่มี | CIDR |
publickey | ไม่มี | คีย์สาธารณะในรูปแบบ PEM |
privatekey | ไม่มี | คีย์ส่วนตัวในรูปแบบ PEM |
certificate | ไม่มี | ใบรับรองในรูปแบบ PEM |
ca | ไม่มี | ใบรับรองสำหรับ CA |
semver | รายการเสริมของข้อ จำกัด | ตรวจสอบเวอร์ชัน semver กับข้อ จำกัด |
type | รายการคีย์ประเภทที่ยอมรับ | ต้องใช้คีย์อย่างน้อยหนึ่งประเภท |
valueset | รายการอาร์กิวเมนต์ที่มีค่า | ค่าที่เป็นไปได้ |
value หรือ = | ค่า | ตรวจสอบมูลค่าเฉพาะ |
gt หรือ > | ค่า | มากกว่า (หมายเลข/สตริง) |
lt หรือ < | ค่า | น้อยกว่า (หมายเลข/สตริง) |
ge หรือ >= | ค่า | มากขึ้นหรือเท่ากับ (หมายเลข/สตริง) |
le หรือ <= | ค่า | น้อยหรือเท่ากับ (หมายเลข/สตริง) |
match หรือ ~= | การแสดงออกปกติ | ค่าสตริงที่ตรงกับนิพจน์ทั่วไป |
list | รายการตัวเลือกของตัวตรวจสอบรายการ | คือรายการและรายการจับคู่ที่ได้รับการตรวจสอบความถูกต้อง |
map | [[<คีย์ตรวจสอบ>,] <รายการตรวจสอบรายการ>] | คือแผนที่และคีย์และรายการที่ตรงกับตัวตรวจสอบความถูกต้อง |
mapfield | <ชื่อฟิลด์> [, <validator>] | รายการที่จำเป็นในแผนที่ |
optionalfield | <ชื่อฟิลด์> [, <validator>] | รายการเสริมในแผนที่ |
and | รายชื่อผู้ตรวจสอบ | ผู้ตรวจสอบทั้งหมดจะต้องประสบความสำเร็จ |
or | รายชื่อผู้ตรวจสอบ | อย่างน้อยหนึ่งคนตรวจสอบความถูกต้องจะต้องประสบความสำเร็จ |
not หรือ ! | เครื่องมือตรวจสอบ | ลบล้างอาร์กิวเมนต์การตรวจสอบความถูกต้อง (s) |
หากการตรวจสอบความถูกต้องสำเร็จค่าจะถูกส่งคืน
เช่น:
dnstarget : (( validate("192.168.42.42", [ "or", "ip", "dnsdomain" ]) ))
ประเมิน
dnstarget : 192.168.42.42
หากการตรวจสอบความถูกต้องล้มเหลวข้อผิดพลาดที่อธิบายถึงเหตุผลความล้มเหลวจะถูกสร้างขึ้น
เช่น:
dnstarget : (( validate("alice+bob", [ "or", "ip", "dnsdomain" ]) ))
ให้ข้อผิดพลาดต่อไปนี้:
*condition 1 failed: (is no ip address: alice+bob and is no dns domain: [a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')])
ผู้ตรวจสอบความถูกต้องอาจเป็นนิพจน์ของแลมบ์ดาโดยใช้อาร์กิวเมนต์อย่างน้อยหนึ่งข้อและส่งคืนค่าบูลีน วิธีนี้เป็นไปได้ที่จะให้ผู้ตรวจสอบตัวเองเป็นส่วนหนึ่งของเอกสาร YAML
เช่น:
val : (( validate( 0, |x|-> x > 1 ) ))
หากมีการประกาศพารามิเตอร์มากกว่าหนึ่งพารามิเตอร์จะต้องระบุอาร์กิวเมนต์เพิ่มเติมเป็นอาร์กิวเมนต์ของตัวตรวจสอบความถูกต้อง อาร์กิวเมนต์แรกเป็นค่าที่ควรตรวจสอบเสมอ
เช่น:
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
ฟังก์ชั่นแลมบ์ดาอาจส่งคืนรายการด้วยองค์ประกอบ 1, 2 หรือ 3 เช่นกัน สามารถใช้เพื่อให้ข้อความที่เหมาะสม
ดัชนี | ความหมาย |
---|---|
0 | ดัชนีแรกมักจะเป็นผลการจับคู่จะต้องประเมินได้เป็นบูลีน |
1 | หากมีสององค์ประกอบดัชนีที่สองคือข้อความที่อธิบายผลลัพธ์จริง |
2 | ที่นี่ดัชนี 1 decribes ข้อความความสำเร็จและ 2 ข้อความความล้มเหลว |
เช่น:
val : (( validate( 6, [|x,m|-> [x > m, "is larger than " m, "is less than or equal to " m], 5] ) ))
เพื่อพูดถึงข้อกำหนดการตรวจสอบความถูกต้องอาจได้รับแบบอินไลน์ดังที่แสดงในตัวอย่างข้างต้น แต่เป็นนิพจน์อ้างอิงเช่นกัน ผู้ตรวจสอบ not
and
or
ผู้ตรวจสอบยอมรับข้อกำหนดการตรวจสอบความถูกต้องที่ซ้อนกันอย่างลึกซึ้ง
เช่น:
dnsrecords :
domain : 1.2.3.4
validator :
- map
- - or # key validator
- dnsdomain
- wildcarddnsdomain
- ip # entry validator
val : (( validate( map, validator) ))
(( check(value,"dnsdomain") ))
check
ฟังก์ชั่นสามารถใช้เพื่อจับคู่โครงสร้าง YAML กับตัวตรวจสอบค่าที่ใช้ YAML ด้วยคำอธิบายการตรวจสอบเดียวกันที่อธิบายไว้แล้วสำหรับการตรวจสอบสามารถใช้งานได้ ผลลัพธ์ของการโทรคือค่าบูลีนที่ระบุผลลัพธ์การจับคู่ มันไม่ล้มเหลวหากการตรวจสอบล้มเหลว
(( error("message") ))
error
ของฟังก์ชั่นสามารถใช้เพื่อทำให้เกิดความล้มเหลวในการประเมินอย่างชัดเจนด้วยข้อความเฉพาะ
ตัวอย่างเช่นสิ่งนี้สามารถใช้เพื่อลดข้อผิดพลาดในการประมวลผลที่ซับซ้อนไปยังข้อความที่มีความหมายโดยการเพิ่มฟังก์ชั่นข้อผิดพลาดเป็นค่าเริ่มต้นสำหรับนิพจน์ที่อาจล้มเหลว
เช่น:
value : (( <some complex potentially failing expression> || error("this was an error my friend") ))
สถานการณ์อื่นอาจละเว้นข้อความเชิงพรรณนาสำหรับฟิลด์ที่ขาดหายไปโดยใช้นิพจน์ข้อผิดพลาดเป็นค่า (เริ่มต้น) สำหรับฟิลด์ที่ตั้งใจจะกำหนดไว้ในต้นขั้วต้นน้ำ
Dynaml รองรับฟังก์ชั่นคณิตศาสตร์ต่างๆ:
จำนวนเต็มที่กลับมา: ceil
floor
round
และ roundtoeven
การกลับมาลอยหรือจำนวนเต็ม: abs
การกลับมาลอย: sin
, cos
, sinh
, cosh
, asin
, acos
, asinh
, acosh
, sqrt
, exp
, log
, log10
,
DynamL รองรับการแปลงประเภทต่าง ๆ ระหว่าง integer
float
, bool
และค่า string
โดยฟังก์ชั่นที่เหมาะสม
เช่น:
value : (( integer("5") ))
แปลงสตริงเป็นค่าจำนวนเต็ม
การแปลงจำนวนเต็มเป็นสตริงยอมรับอาร์กิวเมนต์จำนวนเต็มเพิ่มเติมสำหรับการระบุฐานสำหรับการแปลงเช่น string(55,2)
จะส่งผลให้ "110111"
ฐานเริ่มต้นคือ 10. ฐานจะต้องอยู่ระหว่าง 2 และ 36
Spiff รองรับการเข้าถึงเนื้อหานอกเทมเพลตและไฟล์ย่อย เป็นไปได้ที่จะอ่านไฟล์ดำเนินการคำสั่งและท่อ ฟังก์ชั่นทั้งหมดเหล่านั้นมีอยู่ในสองรสชาติ
sync
ซึ่งมีวัตถุประสงค์เพื่อซิงโครไนซ์การประมวลผลแม่แบบกับสถานะอุทิศ (จัดทำโดยเนื้อหาภายนอก) ที่นี่การแคชการดำเนินการจะไม่เป็นประโยชน์ดังนั้นจึงมีรสชาติที่สอง ทุกฟังก์ชั่นพร้อมใช้งานกับคำต่อท้าย _uncached
(ตัวอย่างเช่น read_uncached()
) (( read("file.yml") ))
อ่านไฟล์และส่งคืนเนื้อหา มีการสนับสนุนสำหรับเนื้อหาสามประเภท: ไฟล์ yaml
ไฟล์ text
และไฟล์ binary
การอ่านในโหมดไบนารีจะส่งผลให้มีสตริงหลายบรรทัดที่เข้ารหัส Base64
หากไฟล์ต่อท้ายคือ .yml
, .yaml
หรือ .json
โดยค่าเริ่มต้นประเภท YAML จะใช้ หากไฟล์ควรอ่านเป็น text
ประเภทนี้จะต้องระบุอย่างชัดเจน ในกรณีอื่น ๆ ทั้งหมดค่าเริ่มต้นคือ text
ดังนั้นการอ่านไฟล์ไบนารี (ตัวอย่างเช่นการเก็บถาวร) จำเป็นต้องระบุโหมด binary
อย่างเร่งด่วน
พารามิเตอร์ที่สองที่เป็นตัวเลือกสามารถใช้เพื่อระบุประเภทการส่งคืนที่ต้องการอย่างชัดเจน: yaml
หรือ text
สำหรับเอกสาร YAML รองรับบางประเภท addtional ได้รับการสนับสนุน: multiyaml
, template
templates
import
และ importmulti
เอกสาร Yaml จะถูกแยกวิเคราะห์และต้นไม้จะถูกส่งคืน องค์ประกอบของต้นไม้สามารถเข้าถึงได้โดยนิพจน์แบบไดนามิกปกติ
นอกจากนี้ไฟล์ YAML อาจมีการแสดงออกของ DynamL อีกครั้ง ทั้งหมดรวมถึงการแสดงออกของ DynamL จะได้รับการประเมินในบริบทของนิพจน์การอ่าน ซึ่งหมายความว่าไฟล์เดียวกันที่รวมอยู่ในสถานที่ต่าง ๆ ในเอกสาร YAML อาจส่งผลให้เกิดต้นไม้ย่อยที่แตกต่างกันขึ้นอยู่กับการแสดงออกของ DynamL ที่ใช้แล้ว
หากเป็นไปได้ที่จะอ่าน Yaml หลายเอกสารด้วย หากได้รับประเภท multiyaml
โหนดรายการที่มีโหนดรูทเอกสาร YAML จะถูกส่งคืน
เอกสาร Yaml หรือ JSON ยังสามารถอ่านเป็น เทมเพลต ได้โดยระบุ template
ประเภท ที่นี่ผลลัพธ์จะเป็นค่าเทมเพลตที่สามารถใช้เช่นเทมเพลตอินไลน์ปกติ หากมีการระบุ templates
จะมีการแม็พเม้นต์หลายรูปแบบกับรายการเทมเพลต
หากประเภทการอ่านถูกตั้งค่าเป็น import
เนื้อหาไฟล์จะถูกอ่านเป็นเอกสาร YAML และโหนดรูทจะใช้แทนนิพจน์ นิพจน์ไดนามิกที่มีศักยภาพที่มีอยู่ในเอกสารจะไม่ได้รับการประเมินด้วยการเชื่อมโยงจริงของนิพจน์พร้อมกับการโทรอ่าน แต่เนื่องจากมันจะเป็นส่วนหนึ่งของไฟล์ต้นฉบับ ดังนั้นโหมดนี้สามารถใช้งานได้หากไม่มีการประมวลผลผลการอ่านเพิ่มเติมหรือค่าที่ส่งมอบไม่ได้ประมวลผล
สิ่งนี้สามารถใช้ร่วมกับการอ้างอิงที่ถูกล่ามโซ่ (สำหรับการตรวจสอบ (( read(...).selection ))
) เพื่อเลือกชิ้นส่วนเฉพาะของเอกสารที่นำเข้า จากนั้น Evaluatio จะทำสำหรับส่วนที่เลือกเท่านั้น การแสดงออกและการอ้างอิงในส่วนอื่น ๆ ไม่ได้ประเมินและเลยและไม่สามารถนำไปสู่ข้อผิดพลาดได้
เช่น:
แม่แบบ. yaml
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
นำเข้า yaml
first :
age : (( ages.alice ))
second :
age : (( ages.bob ))
จะไม่ล้มเหลวเนื่องจากส่วน second
ไม่เคยถูกประเมิน
โหมดนี้ควรใช้ความระมัดระวังเพราะมักจะนำไปสู่ผลลัพธ์ที่ไม่คาดคิด
ประเภทการอ่าน importmulti
สามารถใช้ในการนำเข้าไฟล์ YAML หลายเอกสารเป็นรายการของโหนด
เอกสารข้อความจะถูกส่งคืนเป็นสตริงเดียว
เป็นไปได้ที่จะอ่านเอกสารไบนารีด้วย เนื้อหาไม่สามารถใช้เป็นสตริง (หรือเอกสาร YAML) โดยตรง ดังนั้นจึงต้องระบุ binary
โหมดอ่าน เนื้อหาจะถูกส่งคืนเป็นค่าสตริงหลายบรรทัดที่เข้ารหัส Base64
(( exec("command", arg1, arg2) ))
ดำเนินการคำสั่ง อาร์กิวเมนต์สามารถเป็นนิพจน์แบบไดนามิกใด ๆ รวมถึงนิพจน์อ้างอิงที่ประเมินไปยังรายการหรือแผนที่ รายการหรือแผนที่จะถูกส่งผ่านเป็นอาร์กิวเมนต์เดียวที่มีเอกสาร YAML ที่มีส่วนที่กำหนด
ผลลัพธ์จะถูกกำหนดโดยการแยกวิเคราะห์เอาต์พุตมาตรฐานของคำสั่ง อาจเป็นเอกสาร Yaml หรือสตริงหลายบรรทัดเดียวหรือค่าจำนวนเต็ม เอกสาร Yaml ควรเริ่มต้นด้วยคำนำหน้าเอกสาร ---
หากคำสั่งล้มเหลวการแสดงออกจะถูกจัดการตามที่ไม่ได้กำหนด
เช่น
arg :
- a
- b
list : (( exec( "echo", arg ) ))
string : (( exec( "echo", arg.[0] ) ))
อัตราผลตอบแทน
arg :
- a
- b
list :
- a
- b
string : a
exec
สามารถเรียกใช้กับอาร์กิวเมนต์รายการเดียวที่อธิบายบรรทัดคำสั่งได้อย่างสมบูรณ์
คำสั่งเดียวกันจะถูกดำเนินการเพียงครั้งเดียวเท่านั้นแม้ว่าจะใช้ในการแสดงออกหลายครั้ง
(( pipe(data, "command", arg1, arg2) ))
ดำเนินการคำสั่งและป้อนอินพุตมาตรฐานด้วยข้อมูลเฉพาะ อาร์กิวเมนต์คำสั่งจะต้องเป็นสตริง อาร์กิวเมนต์สำหรับคำสั่งสามารถเป็นนิพจน์ไดนามิกใด ๆ รวมถึงนิพจน์อ้างอิงที่ประเมินไปยังรายการหรือแผนที่ รายการหรือแผนที่จะถูกส่งผ่านเป็นอาร์กิวเมนต์เดียวที่มีเอกสาร YAML ที่มีส่วนที่กำหนด
สตรีมอินพุตถูกสร้างขึ้นจากข้อมูลที่กำหนด หากนี่เป็นประเภทง่าย ๆ ที่ใช้การเป็นตัวแทนสตริง มิฉะนั้นเอกสาร YAML จะถูกสร้างขึ้นจากข้อมูลอินพุต ผลลัพธ์จะถูกกำหนดโดยการแยกวิเคราะห์เอาต์พุตมาตรฐานของคำสั่ง อาจเป็นเอกสาร Yaml หรือสตริงหลายบรรทัดเดียวหรือค่าจำนวนเต็ม เอกสาร Yaml ควรเริ่มต้นด้วยคำนำหน้าเอกสาร ---
หากคำสั่งล้มเหลวการแสดงออกจะถูกจัดการตามที่ไม่ได้กำหนด
เช่น
data :
- a
- b
list : (( pipe( data, "tr", "a", "z") ))
อัตราผลตอบแทน
arg :
- a
- b
list :
- z
- b
pipe
สามารถเรียกใช้กับข้อมูลและอาร์กิวเมนต์รายการอธิบายบรรทัดคำสั่งได้อย่างสมบูรณ์
คำสั่งเดียวกันจะถูกดำเนินการเพียงครั้งเดียวเท่านั้นแม้ว่าจะใช้ในการแสดงออกหลายครั้ง
(( write("file.yml", data) ))
เขียนไฟล์และส่งคืนเนื้อหา หากผลลัพธ์สามารถแยกวิเคราะห์เป็นเอกสาร Yaml ได้เอกสารจะถูกส่งคืน อาร์กิวเมนต์ตัวเลือกที่ 3 สามารถใช้เพื่อผ่านตัวเลือกการเขียน อาร์กิวเมนต์ตัวเลือกอาจเป็นจำนวนเต็มที่แสดงสิทธิ์ไฟล์ (ค่าเริ่มต้นคือ 0644
) หรือสตริงคั่นด้วยเครื่องหมายจุลภาคพร้อมตัวเลือก ตัวเลือกที่รองรับคือ
binary
: ข้อมูลคือฐาน 64 ถอดรหัสก่อนเขียน0
ชั้นนำคือการระบุค่าแปดเท่า (( tempfile("file.yml", data) ))
เขียนไฟล์ชั่วคราว AA และส่งคืนชื่อพา ธ อาร์กิวเมนต์ตัวเลือกที่ 3 สามารถใช้เพื่อผ่านตัวเลือกการเขียน โดยพื้นฐานแล้วมันมีพฤติกรรมเหมือน write
ความสนใจ : ไฟล์ชั่วคราวมีอยู่ระหว่างการประมวลผลการผสานเท่านั้น มันจะถูกลบหลังจากนั้น
มันสามารถใช้งานได้เช่นเพื่อให้อาร์กิวเมนต์ไฟล์ชั่วคราวสำหรับฟังก์ชั่น exec
(( lookup_file("file.yml", list) ))
ค้นหาไฟล์เป็นรายการของไดเรกทอรี ผลลัพธ์คือรายการไฟล์ที่มีอยู่ ด้วย lookup_dir
เป็นไปได้ที่จะค้นหาไดเรกทอรีแทน
หากไม่พบไฟล์ที่มีอยู่รายการว่างจะถูกส่งคืน
เป็นไปได้ที่จะผ่านหลายรายการหรืออาร์กิวเมนต์สตริงเพื่อเขียนเส้นทางการค้นหา
(( mkdir("dir", 0755) ))
สร้างไดเรกทอรีและไดเรกทอรีระดับกลางทั้งหมดหากยังไม่มีอยู่
ส่วนสิทธิ์เป็นทางเลือก (ค่าเริ่มต้น 0755) เส้นทางของไดเรกทอรีอาจได้รับโดย atring เช่นค่าหรือเป็นรายการของส่วนประกอบพา ธ
(( list_files(".") ))
แสดงรายการไฟล์ในไดเร็กทอรี ผลลัพธ์คือรายการไฟล์ที่มีอยู่ ด้วย list_dirs
เป็นไปได้ที่จะแสดงรายการไดเรกทอรีแทน
(( archive(files, "tar") ))
สร้างเก็บถาวรของประเภทที่กำหนด (ค่าเริ่มต้นคือ tar
) ที่มีไฟล์ที่แสดงรายการ ผลที่ได้คือการเข้ารหัสที่เข้ารหัส Base64
ประเภทเก็บถาวรที่รองรับคือ tar
และ targz
files
อาจเป็นรายการหรือแผนที่ของรายการไฟล์ ในกรณีของแผนที่ปุ่มแผนที่จะใช้เป็นค่าเริ่มต้นสำหรับเส้นทางไฟล์ รายการไฟล์เป็นแผนที่ที่มีฟิลด์ต่อไปนี้:
สนาม | พิมพ์ | ความหมาย |
---|---|---|
path | เชือก | เป็นทางเลือกสำหรับแผนที่เส้นทางไฟล์ในไฟล์เก็บถาวรเริ่มต้นด้วยคีย์แผนที่ |
mode | สตริง int หรือ int | โหมดไฟล์หรือตัวเลือกการเขียน โดยพื้นฐานแล้วมันจะทำงานเหมือนอาร์กิวเมนต์ตัวเลือกสำหรับ write |
data | ใดๆ | เนื้อหาไฟล์ Yaml จะถูกจัดให้เป็นเอกสาร Yaml หาก mode ระบุโหมดไบนารีค่าสตริงจะถูกถอดรหัส BASE64 |
base64 | เชือก | Base64 ข้อมูลไบนารีที่เข้ารหัส |
เช่น:
yaml :
alice : 26
bob : 27
files :
" data/a/test.yaml " :
data : (( yaml ))
" data/b/README.md " :
data : |+
### Test Docu
**Note**: This is a test
archive : (( archive(files,"targz") ))
content : (( split("n", exec_uncached("tar", "-tvf", tempfile(archive,"binary"))) ))
อัตราผลตอบแทน:
archive : |-
H4sIAAAAAAAA/+zVsQqDMBAG4Mx5igO3gHqJSQS3go7tUHyBqIEKitDEoW9f
dLRDh6KlJd/yb8ll+HOd8SY1qbfOJw8zDmQHiIhayjURcZuIQhOeSZlphVwL
glwsAXvM8mJ23twJ4qfnbB/3I+I4pmboW1uA0LSZmgJETr89VXCUtf9Neq1O
5blKxm6PO972X/FN/7nKVej/EaIogto6D+XUzpQydpm8ZayA+tY76B0YWHYD
DV9CEATBf3kGAAD//5NlAmIADAAA
content :
- -rw-r--r-- 0/0 22 2019-03-18 09:01 data/a/test.yaml
- -rw-r--r-- 0/0 41 2019-03-18 09:01 data/b/README.md
files :
data/a/test.yaml :
data :
alice : 26
bob : 27
data/b/README.md :
data : |+
### Test Docu
**Note**: This is a test
yaml :
alice : 26
bob : 27
Spiff รองรับการจัดการชื่อเวอร์ชันความหมาย รองรับฟังก์ชั่นทั้งหมดจากแพ็คเกจ MasterMinds Semver ที่ยอมรับเวอร์ชันที่มีหรือไม่มีชั้นนำ v
(( semver("v1.2-beta.1") ))
ตรวจสอบว่าสตริงที่กำหนดเป็นเวอร์ชันความหมายหรือไม่และส่งคืนแบบฟอร์มปกติ (โดยไม่ต้องนำ v
และส่วนการเปิดตัวที่สมบูรณ์ด้วยหมายเลขรุ่น Major, Minor และ Patch และหมายเลขแพทช์)
เช่น:
normalized : (( semver("v1.2-beta.1") ))
แก้ไข
normalized : 1.2.0-beta.1
(( semverrelease("v1.2.3-beta.1") ))
ส่งคืนส่วนการเปิดตัวของเวอร์ชันความหมายโดยละเว้นข้อมูลเมตาและข้อมูลล่วงหน้า
เช่น:
release : (( semverrelease("v1.2.3-beta.1") ))
แก้ไข
release : v1.2.3
หากอาร์กิวเมนต์สตริงเพิ่มเติมได้รับฟังก์ชั่นนี้แทนที่การเปิดตัวโดยการเปิดตัวของเวอร์ชันความหมายที่กำหนดเพื่อรักษาข้อมูลเมตาและข้อมูลก่อนการเปิดตัว
เช่น:
new : (( semverrelease("1.2.3-beta.1", "1.2.1) ))
แก้ไข
new : 1.2.1-beta.1
(( semvermajor("1.2.3-beta.1") ))
กำหนดหมายเลขเวอร์ชันหลักของเวอร์ชันความหมายที่กำหนด ผลที่ได้คือจำนวนเต็ม
เช่น:
major : (( semvermajor("1.2.3-beta.1") ))
แก้ไข
major : 1
ฟังก์ชั่น semverincmajor
สามารถใช้เพื่อเพิ่มหมายเลขเวอร์ชันหลักและรีเซ็ตเวอร์ชันรองเวอร์ชันแพตช์และคำต่อท้ายที่วางจำหน่าย
เช่น:
new : (( semverincmajor("1.2.3-beta.1") ))
แก้ไข
new : 2.0.0
(( semverminor("1.2.3-beta.1") ))
กำหนดหมายเลขรุ่นรองของเวอร์ชันความหมายที่กำหนด ผลที่ได้คือจำนวนเต็ม
เช่น:
minor : (( semverminor("1.2.3-beta.1") ))
แก้ไข
minor : 2
ฟังก์ชั่น semverincminor
สามารถใช้เพื่อเพิ่มหมายเลขรุ่นรองและรีเซ็ตเวอร์ชันแพตช์และวางจำหน่ายคำต่อท้าย
เช่น:
new : (( semverincmajor("v1.2.3-beta.1") ))
แก้ไข
new : v1.3.0
(( semverpatch("1.2.3-beta.1") ))
กำหนดหมายเลขเวอร์ชันแพตช์ของเวอร์ชันความหมายที่กำหนด ผลที่ได้คือจำนวนเต็ม
เช่น:
patch : (( semverpatch("1.2.3-beta.1") ))
แก้ไข
patch : 3
ฟังก์ชั่น semverincpatch
สามารถใช้เพื่อเพิ่มหมายเลขเวอร์ชันแพตช์หรือรีเซ็ตคำต่อท้ายรุ่น หากมีคำต่อท้าย rlease พวกเขาจะถูกลบออกและข้อมูลการเปิดตัวจะไม่เปลี่ยนแปลงมิฉะนั้นหมายเลขเวอร์ชันแพตช์จะเพิ่มขึ้น
เช่น:
final : (( semverincpatch("1.2.3-beta.1") ))
new : (( semverincpatch(final) ))
แก้ไข
final : 1.2.3
new : 1.2.4
(( semverprerelease("1.2.3-beta.1") ))
กำหนด prerelease ของเวอร์ชันความหมายที่กำหนด ผลลัพธ์คือสตริง
เช่น:
prerelease : (( semverprerelease("1.2.3-beta.1") ))
แก้ไข
prerelease : beta.1
หากอาร์กิวเมนต์สตริงเพิ่มเติมได้รับการตั้งค่าฟังก์ชันนี้ให้แทนที่หรือล้าง (ถ้าตั้งค่าเป็นสตริงว่าง) prerelease
เช่น:
new : (( semverprerelease("1.2.3-beta.1", "beta.2) ))
แก้ไข
new : 1.2.3-beta.2
(( semvermetadata("1.2.3+demo") ))
กำหนดข้อมูลเมตาของเวอร์ชันความหมายที่กำหนด ผลลัพธ์คือสตริง
เช่น:
metadata : (( semvermetadata("1.2.3+demo") ))
แก้ไข
metadata : demo
หากอาร์กิวเมนต์สตริงเพิ่มเติมได้รับการตั้งค่าฟังก์ชั่นนี้ให้แทนที่หรือล้าง (ถ้าตั้งค่าเป็นสตริงว่าง) ข้อมูลเมตา
เช่น:
new : (( semvermetadata("1.2.3-test", "demo) ))
แก้ไข
new : 1.2.3+demo
(( semvercmp("1.2.3", 1.2.3-beta.1") ))
เปรียบเทียบสองรุ่นความหมาย prerelease มี ขนาดเล็กกว่า การเปิดตัวครั้งสุดท้ายเสมอ ผลลัพธ์คือจำนวนเต็มที่มีค่าต่อไปนี้:
ผลลัพธ์ | ความหมาย |
---|---|
-1 | เวอร์ชันแรกคือก่อนเวอร์ชันที่สอง |
0 | ทั้งสองเวอร์ชันเท่ากัน |
1 | Versuon แรกคือหลังจากอันที่สอง |
เช่น:
compare : (( semvercmp("1.2.3", "1.2.3-beta.1") ))
แก้ไข
compare : 1
(( semvermatch("1.2.3", "~1.2") ))
จับคู่เวอร์ชันความหมายที่กำหนดไว้กับรายการข้อโต้แย้ง ผลที่ได้คือบูลีน เป็นไปได้ที่จะระบุข้อ จำกัด ของเวอร์ชันจำนวนใด ๆ หากไม่มีข้อ จำกัด ฟังก์ชั่นเพียงตรวจสอบว่าสตริงที่กำหนดเป็นเวอร์ชันความหมายหรือไม่
เช่น:
match : (( semvermatch("1.2.3", "~1.2") ))
แก้ไข
match : true
รายการข้อมูลจำเพาะข้อ จำกัด ที่เป็นไปได้ที่สมบูรณ์สามารถพบได้ที่นี่
(( semversort("1.2.3", "1.2.1") ))
เรียงลำดับรายการของเวอร์ชันตามลำดับจากน้อยไปมาก v
ชั้นนำได้รับการเก็บรักษาไว้
เช่น:
sorted : (( semversort("1.2.3", "1.2.1") ))
แก้ไข
sorted :
- 1.2.1
- 1.2.3
รายการเวอร์ชันที่จะเรียงลำดับอาจถูกระบุด้วยอาร์กิวเมนต์รายการเดียว
Spiff รองรับฟังก์ชั่นที่มีประโยชน์บางอย่างในการทำงานกับใบรับรองและคีย์ X509 โปรดดูส่วนที่เป็นประโยชน์ในการทราบเพื่อค้นหาเคล็ดลับบางอย่างสำหรับการให้สถานะ
(( x509genkey(spec) ))
ฟังก์ชั่นนี้สามารถใช้สร้างคีย์ RSA ส่วนตัวหรือ ECDSA ผลลัพธ์จะเป็นคีย์ที่เข้ารหัส PEM เป็นค่าสตริงหลายบรรทัด หากขนาดคีย์ (จำนวนเต็มหรือสตริง) เป็นอาร์กิวเมนต์คีย์ RSA จะถูกสร้างขึ้นด้วยขนาดคีย์ที่กำหนด (เช่น 2048) ให้หนึ่งในค่าสตริง
ฟังก์ชั่นจะสร้างคีย์ ECDSA ที่เหมาะสม
เช่น:
keys :
key : (( x509genkey(2048) ))
แก้ไขสิ่งที่ชอบ
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
(( x509publickey(key) ))
สำหรับคีย์หรือใบรับรองที่กำหนดในรูปแบบ PEM (ตัวอย่างเช่นที่สร้างขึ้นด้วยฟังก์ชั่น X509GENKEY) ฟังก์ชั่นนี้จะแยกคีย์สาธารณะและส่งคืนอีกครั้งในรูปแบบ PEM เป็นสตริงหลายบรรทัด
เช่น:
keys :
key : (( x509genkey(2048) ))
public : (( x509publickey(key)
แก้ไขสิ่งที่ชอบ
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
public : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rnsCxMx
vSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb325Bf/
...
VzYqyeQyvvRbNe73BXc5temCaQayzsbghkoWK+Wrc33yLsvpeVQBcB93Xhus+Lt1
1lxsoIrQf/HBsiu/5Q3M8L6klxeAUcDbYwIDAQAB
-----END RSA PUBLIC KEY-----
ในการสร้างคีย์สาธารณะ SSH สามารถตั้งค่าอาร์กิวเมนต์รูปแบบเพิ่มเติมที่เป็นตัวเลือกได้เป็น ssh
ผลลัพธ์จะเป็นรูปแบบคีย์สาธารณะปกติที่ใช้งานได้สำหรับ SSH รูปแบบเริ่มต้นคือ pem
ที่ให้รูปแบบเอาต์พุต PEM ที่แสดงด้านบน
คีย์ RSA นั้นเป็นค่าเริ่มต้นในรูปแบบ PKCS#1 ( RSA PUBLIC KEY
) ใน PEM หากจำเป็นต้องใช้รูปแบบ pkix ทั่วไป ( PUBLIC KEY
) ต้องให้อาร์กิวเมนต์รูปแบบ pkix
การใช้รูปแบบ ssh
ฟังก์ชั่นนี้ยังสามารถใช้ในการแปลงคีย์สาธารณะที่จัดรูปแบบ PEM เป็นคีย์ SSH
(( x509cert(spec) ))
ฟังก์ชั่น x509cert
สร้างใบรับรองที่ลงนามในเครื่องไม่ว่าจะเซ็นชื่อหนึ่งหรือใบรับรองที่ลงนามโดย CA ที่กำหนด มันส่งคืนใบรับรองการเข้ารหัส PEM เป็นค่าสตริงหลายบรรทัด
พารามิเตอร์ ข้อมูลจำเพาะ เดี่ยวใช้แผนที่พร้อมฟิลด์เสริมและไม่เสริมที่ใช้เพื่อระบุข้อมูลใบรับรอง อาจเป็นนิพจน์แผนที่แบบอินไลน์หรือการอ้างอิงแผนที่ใด ๆ ในเอกสารที่เหลือของ YAML
มีการสังเกตฟิลด์แผนที่ต่อไปนี้:
ชื่อฟิลด์ | พิมพ์ | ที่จำเป็น | ความหมาย |
---|---|---|---|
commonName | เชือก | ไม่จำเป็น | ฟิลด์ชื่อสามัญของเรื่อง |
organization | สตริงหรือรายการสตริง | ไม่จำเป็น | สาขาวิชาของเรื่อง |
country | สตริงหรือรายการสตริง | ไม่จำเป็น | เขตข้อมูลประเทศของเรื่อง |
isCA | บูล | ไม่จำเป็น | ตัวเลือก CA ของใบรับรอง |
usage | สตริงหรือรายการสตริง | ที่จำเป็น | คีย์การใช้งานสำหรับใบรับรอง (ดูด้านล่าง) |
validity | จำนวนเต็ม | ไม่จำเป็น | ช่วงเวลาที่ถูกต้องเป็นชั่วโมง |
validFrom | เชือก | ไม่จำเป็น | เวลาเริ่มต้นในรูปแบบ "1 ม.ค. 01:22:31 2019" |
hosts | สตริงหรือรายการสตริง | ไม่จำเป็น | รายการชื่อ DNS หรือที่อยู่ IP |
privateKey | เชือก | จำเป็นหรือ PublicKey | คีย์ส่วนตัวเพื่อสร้างใบรับรองสำหรับ |
publicKey | เชือก | จำเป็นหรือ PrivateKey | คีย์สาธารณะเพื่อสร้างใบรับรองสำหรับ |
caCert | เชือก | ไม่จำเป็น | ใบรับรองที่จะลงชื่อเข้าใช้ |
caPrivateKey | เชือก | ไม่จำเป็น | คีย์ Priavte สำหรับ caCert |
สำหรับใบรับรองที่ลงนามด้วยตนเองต้องตั้งค่าฟิลด์ privateKey
publicKey
และเขต ca
ควรละเว้น หากมีการให้สนาม caCert
จำเป็นต้องใช้สนาม caKey
ด้วย หากฟิลด์ privateKey
ได้รับพร้อมกับ caCert
คีย์สาธารณะสำหรับใบรับรองจะถูกสกัดจากคีย์ส่วนตัว
ฟิลด์เพิ่มเติมจะถูกละเว้นอย่างเงียบ ๆ
รองรับคีย์การใช้งานต่อไปนี้ (ถูกละเว้นกรณี):
สำคัญ | ความหมาย |
---|---|
Signature | x509.KeyUsagedigitalsIgnature |
Commitment | x509.KeyusagecontentCommitment |
KeyEncipherment | x509.KeyUsageKeyencipherment |
DataEncipherment | x509.Keyusagedataencipherment |
KeyAgreement | x509.KeyUsageKeyAgreement |
CertSign | x509.Keyusagecertsign |
CRLSign | x509.KeyUsagecrlsign |
EncipherOnly | x509.KeyUsageencipheronly |
DecipherOnly | x509.KeyUsagedecipheronly |
Any | x509.extkeyusageany |
ServerAuth | x509.extkeyusageserverauth |
ClientAuth | x509.extkeyusageclientauth |
codesigning | X509.EXTKEYUSAGECODESIGNING |
EmailProtection | X509.EXTKEYUSAGEEMAILPROTECTION |
IPSecEndSystem | X509.EXTKEYUSAGEIPSECEN |
IPSecTunnel | X509.EXTKEYUSAGEIPSECTUNNEL |
IPSecUser | X509.EXTKEYUSAGEIPSECUSER |
TimeStamping | x509.extkeyusagetimestamping |
OCSPSigning | X509.EXTKEYUSAGEOCSPSIGNING |
MicrosoftServerGatedCrypto | x509 |
NetscapeServerGatedCrypto | x509.extkeyusagenetscapeservergatedcrypto |
MicrosoftCommercialCodeSigning | x509 |
MicrosoftKernelCodeSigning | X509 |
เช่น:
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : Uwe Krueger
privateKey : (( data.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
data :
cakey : (( x509genkey(2048) ))
cacert : (( x509cert(spec.ca) ))
สร้างใบรับรองรูทที่ลงนามด้วยตนเองและแก้ไขสิ่งต่างๆ
cakey : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
cacert : |+
-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIQb5ex4iGfyCcOa1RvnKSkMDANBgkqhkiG9w0BAQsFADAk
MQ8wDQYDVQQKEwZTQVAgU0UxETAPBgNVBAMTCGdhcmRlbmVyMB4XDTE4MTIzMTE0
...
pOUBE3Tgim5rnpa9K9RJ/m8IVqlupcONlxQmP3cCXm/lBEREjODPRNhU11DJwDdJ
5fd+t5SMEit2BvtTNFXLAwz48EKTxsDPdnHgiQKcbIV8NmgUNPHwXaqRMBLqssKl
Cyvds9xGtAtmZRvYNI0=
-----END CERTIFICATE-----
(( x509parsecert(cert) ))
ฟังก์ชั่นนี้วิเคราะห์ใบรับรองที่ให้ไว้ในรูปแบบ PEM และส่งคืนแผนที่ของฟิลด์:
ชื่อฟิลด์ | พิมพ์ | ที่จำเป็น | ความหมาย |
---|---|---|---|
commonName | เชือก | ไม่จำเป็น | ฟิลด์ชื่อสามัญของเรื่อง |
organization | รายการสตริง | ไม่จำเป็น | สาขาวิชาของเรื่อง |
country | รายการสตริง | ไม่จำเป็น | เขตข้อมูลประเทศของเรื่อง |
isCA | บูล | เสมอ | ตัวเลือก CA ของใบรับรอง |
usage | รายการสตริง | เสมอ | คีย์การใช้งานสำหรับใบรับรอง (ดูด้านล่าง) |
validity | จำนวนเต็ม | เสมอ | validity interval in hours |
validFrom | เชือก | เสมอ | start time in the format "Jan 1 01:22:31 2019" |
validUntil | เชือก | เสมอ | start time in the format "Jan 1 01:22:31 2019" |
hosts | string list | ไม่จำเป็น | List of DNS names or IP addresses |
dnsNames | string list | ไม่จำเป็น | List of DNS names |
ipAddresses | string list | ไม่จำเป็น | List of IP addresses |
publicKey | เชือก | เสมอ | public key to generate the certificate for |
eg:
data :
<< : (( &temporary ))
spec :
commonName : test
organization : org
validity : 100
isCA : true
privateKey : (( gen.key ))
hosts :
- localhost
- 127.0.0.1
usage :
- ServerAuth
- ClientAuth
- CertSign
gen :
key : (( x509genkey() ))
cert : (( x509cert(spec) ))
cert : (( x509parsecert(data.gen.cert) ))
resolves to
cert :
commonName : test
dnsNames :
- localhost
hosts :
- 127.0.0.1
- localhost
ipAddresses :
- 127.0.0.1
isCA : true
organization :
- org
publickey : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA+UIZQUTa/j+WlXC394bccBTltV+Ig3+zB1l4T6Vo36pMBmU4JIkJ
...
TCsrEC5ey0cCeFij2FijOJ5kmm4cK8jpkkb6fLeQhFEt1qf+QqgBw3targ3LnZQf
uE9t5MIR2X9ycCQSDNBxcuafHSwFrVuy7wIDAQAB
-----END RSA PUBLIC KEY-----
usage :
- CertSign
- ServerAuth
- ClientAuth
validFrom : Mar 11 15:34:36 2019
validUntil : Mar 15 19:34:36 2019
validity : 99 # yepp, that's right, there has already time passed since the creation
spiff supports some useful functions to work with wireguard keys. Please refer also to the Useful to Know section to find some tips for providing state.
(( wggenkey() ))
This function can be used generate private wireguard key. The result will base64 encoded.
eg:
keys :
key : (( wggenkey() ))
resolves to something like
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
(( wgpublickey(key) ))
For a given key (for example generated with the wggenkey function) this function extracts the public key and returns it again in base64 format-
eg:
keys :
key : (( wggenkey() ))
public : (( wgpublickey(key)
resolves to something like
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
public : n405KfwLpfByhU9pOu0A/ENwp0njcEmmQQJvfYHHQ2M=
(( lambda |x|->x ":" port ))
Lambda expressions can be used to define additional anonymous functions. They can be assigned to yaml nodes as values and referenced with path expressions to call the function with approriate arguments in other dynaml expressions. For the final document they are mapped to string values.
There are two forms of lambda expressions. ในขณะที่
lvalue : (( lambda |x|->x ":" port ))
yields a function taking one argument by directly taking the elements from the dynaml expression,
string : " |x|->x " : " port "
lvalue : (( lambda string ))
evaluates the result of an expression to a function. The expression must evaluate to a function or string. If the expression is evaluated to a string it parses the function from the string.
Since the evaluation result of a lambda expression is a regular value, it can also be passed as argument to function calls and merged as value along stub processing.
A complete example could look like this:
lvalue : (( lambda |x,y|->x + y ))
mod : (( lambda|x,y,m|->(lambda m)(x, y) + 3 ))
value : (( .mod(1,2, lvalue) ))
อัตราผลตอบแทน
lvalue : lambda |x,y|->x + y
mod : lambda|x,y,m|->(lambda m)(x, y) + 3
value : 6
If a complete expression is a lambda expression the keyword lambda
can be omitted.
Lambda expressions evaluate to lambda values, that are used as final values in yaml documents processed by spiff .
Note : If the final document still contains lambda values, they are transferred to a textual representation. It is not guaranteed that this representation can correctly be parsed again, if the document is re-processed by spiff . Especially for complex scoped and curried functions this is not possible.
Therefore function nodes should always be temporary or local to be available during processing or merging, but being omitted for the final document.
A typical function call uses positional arguments. Here the given arguments satisfy the declared function parameters in the given order. For lambda values it is also possible to use named arguments in the call expression. Here an argument is assigned to a dedicated parameter as declared by the lambda expression. The order of named arguments can be arbitrarily chosen.
eg:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, b=2, a=1) ))
It is also posible to combine named with positional arguments. Hereby the positional arguments must follow the named ones.
eg:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, 1, 2) ))
The same argument MUST NOT be satified by both, a named and a positional argument.
Instead of using the parameter name it is also possible to use the parameter index, instead.
eg:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(3=1, 1) ))
As such, this feature seems to be quite useless, but it shows its power if combined with optional parameters or currying as shown in the next paragraphs.
A lambda expression might refer to absolute or relative nodes of the actual yaml document of the call. Relative references are evaluated in the context of the function call. ดังนั้น
lvalue : (( lambda |x,y|->x + y + offset ))
offset : 0
values :
offset : 3
value : (( .lvalue(1,2) ))
yields 6
for values.value
.
Besides the specified parameters, there is an implicit name ( _
), that can be used to refer to the function itself. It can be used to define self recursive function. Together with the logical and conditional operators a fibunacci function can be defined:
fibonacci : (( lambda |x|-> x <= 0 ? 0 :x == 1 ? 1 :_(x - 2) + _( x - 1 ) ))
value : (( .fibonacci(5) ))
yields the value 8
for the value
property.
By default reference expressions in a lambda expression are evaluated in the static scope of the lambda dedinition followed by the static yaml scope of the caller. Absolute references are always evalated in the document scope of the caller.
The name _
can also be used as an anchor to refer to the static definition scope of the lambda expression in the yaml document that was used to define the lambda function. Those references are always interpreted as relative references related to the this static yaml document scope. There is no denotation for accessing the root element of this definition scope.
Relative names can be used to access the static definition scope given inside the dynaml expression (outer scope literals and parameters of outer lambda parameters)
eg:
env :
func : (( |x|->[ x, scope, _, _.scope ] ))
scope : definition
call :
result : (( env.func("arg") ))
scope : call
yields the result
list:
call :
result :
- arg
- call
- (( lambda|x|->[x, scope, _, _.scope] )) # the lambda expression as lambda value
- definition
This also works across multiple stubs. The definition context is the stub the lambda expression is defined in, even if it is used in stubs down the chain. Therefore it is possible to use references in the lambda expression, not visible at the caller location, they carry the static yaml document scope of their definition with them.
Inner lambda expressions remember the local binding of outer lambda expressions. This can be used to return functions based on arguments of the outer function.
eg:
mult : (( lambda |x|-> lambda |y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
yields 6
for property value
.
Trailing parameters may be defaulted in the lambda expression by assigning values in the declaration. Those parameter are then optional, it is not required to specify arguments for those parameters in function calls.
eg:
mult : (( lambda |x,y=2|-> x * y ))
value : (( .mult(3) ))
yields 6
for property value
.
It is possible to default all parameters of a lambda expression. The function can then be called without arguments. There might be no non-defaulted parameters after a defaulted one.
A call with positional arguments may only omit arguments for optional parameters from right to left. If there should be an explicit argument for the right most parameter, arguments for all parameters must be specified or named arguments must be used. Here the desired optional parameter can explicitly be set prior to the regular positional arguments.
eg:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=3, 2) ))
evaluates result
to
result :
a : 2
b : 1
c : 3
The expression for the default does not need to be a constant value or even expression, it might refer to other nodes in the yaml document. The default expression is always evaluated in the scope of the lambda expression declaration at the time the lambda expression is evaluated.
eg:
stub.yaml
default : 2
mult : (( lambda |x,y=default * 2|-> x * y ))
template.yaml
mult : (( merge ))
scope :
default : 3
value : (( .mult(3) ))
evaluates value
to 12
The last parameter in the parameter list of a lambda expression may be a varargs parameter consuming additional argument in a fnction call. This parameter is always a list of values, one entry per additional argument.
A varargs parameter is denoted by a ...
following the last parameter name.
eg:
func : (( |a,b...|-> [a] b ))
result : (( .func(1,2,3) ))
yields the list [1, 2, 3]
for property result
.
If no argument is given for the varargs parameter its value is the empty list.
The ...
operator can also be used for inline list expansion.
If a vararg parameter should be set by a named argument its value must be a list.
Using the currying operator ( *(
) a lambda function may be transformed to another function with less parameters by specifying leading argument values.
The result is a new function taking the missing arguments (currying) and using the original function body with a static binding for the specified parameters.
eg:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult*(2) ))
value : (( .mult2(3) ))
Currying may be combined with defaulted parameters. But the resulting function does not default the leading parameters, it is just a new function with less parameters pinning the specified ones.
If the original function uses a variable argument list, the currying may span any number of the variable argument part, but once at least one such argument is given, the parameter for the variable part is satisfied. It cannot be extended by a function call of the curried function.
eg:
func : (( |a,b...|->join(a,b) ))
func1 : (( .func*(",","a","b")))
# invalid: (( .func1("c") ))
value : (( .func1() ))
evaluates value
to "a,b"
.
It is also possible to use currying for builtin functions, like join
.
eg:
stringlist : (( join*(",") ))
value : (( .stringlist("a", "b") ))
evaluates value
to "a,b"
.
There are several builtin functions acting on unevaluated or unevaluatable arguments, like defined
. For these functions currying is not possible.
Using positional arguments currying is only possible from right to left. But currying can also be done for named arguments. Here any parameter combination, regardless of the position in the parameter list, can be preset. The resulting function then has the unsatisfied parameters in their original order. Switching the parameter order is not possible.
eg:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
curry : (( .func(c=3, 2) ))
result : (( .curry(5) ))
evalutes result
to
result :
a : 2
b : 5
c : 3
The resulting function keeps the parameter b
. Hereby the default value will be kept. Therefore it can just be called without argument ( .curry()
), which would produce
result :
a : 2
b : 1
c : 3
ความสนใจ :
For compatibility reasons currying is also done, if a lambda function without defaulted parameters is called with less arguments than declared parameters.
This behaviour is deprecated and will be removed in the future. It is replaced by the currying operator.
eg:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
evaluates value
to 6.
(( catch[expr|v,e|->v] ))
This expression evaluates an expression ( expr
) and then executes a lambda function with the evaluation state of the expression. It always succeeds, even if the expression fails. The lambda function may take one or two arguments, the first is always the evaluated value (or nil
in case of an error). The optional second argument gets the error message the evaluation of the expression failed (or nil
otherwise)
The result of the function is the result of the whole expression. If the function fails, the complete expression fails.
eg:
data :
fail : (( catch[1 / 0|v,e|->{$value=v, $error=e}] ))
valid : (( catch[5 * 5|v,e|->{$value=v, $error=e}] ))
resolves to
data :
fail :
error : division by zero
value : null
valid :
error : null
value : 25
(( sync[expr|v,e|->defined(v.field),v.field|10] ))
If an expression expr
may return different results for different evaluations, it is possible to synchronize the final output with a dedicated condition on the expression value. Such an expression could, for example, be an uncached read
, exec
or pipe
call.
The second element must evaluate to a lambda value, given by either a regular expression or by a lambda literal as shown in the title. It may take one or two arguments, the actual value of the value expression and optionally an error message in case of a failing evaluation. The result of the evaluation of the lamda expression decides whether the state of the evaluation of the value expression is acceptable ( true
) or not ( false
).
If the value is accepted, an optional third expression is used to determine the final result of the sync[]
expression. It might be given as an expression evaluating to a lambda value, or by a comma separated expression using the same binding as the preceeding lambda literal. If not given, the value of the synched expression is returned.
If the value is not acceptable, the evaluation is repeated until a timeout applies. The timeout in seconds is given by an optional fourth expression (default is 5 min). Either the fourth, or the both, the third and the fourth elements may be omitted.
The lambda values might be given as literal, or by expression, leading to the following flavors:
sync[expr|v,e|->cond,value|10]
sync[expr|v,e|->cond|valuelambda|10]
sync[expr|v,e|->cond|v|->value|10]
sync[expr|condlambda|valuelambda|10]
sync[expr|condlambda|v|->value|10]
with or without the timeout expression.
eg:
data :
alice : 25
result : (( sync[data|v|->defined(v.alice),v.alice] ))
resolves to
data :
alice : 25
result : 25
This example is quite useless, because the sync expression is a constant. It just demonstrates the usage.
Mappings are used to produce a new list from the entries of a list or map , or a new map from entries of a map containing the entries processed by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the mapping function: It can be inlined as in (( map[list|x|->x ":" port] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( map[list|mapping.expression))
(here the mapping is taken from the property mapping.expression
, which should hold an approriate lambda function).
The mapping comes in two target flavors: with []
or {}
in the syntax. The first flavor always produces a list from the entries of the given source. The second one takes only a map source and produces a filtered or transformed map .
Additionally the mapping uses three basic mapping behaviours:
map
. Here the result of the lambda function is used as new value to replace the original one. หรือselect
. Here the result of the lambda function is used as a boolean to decide whether the entry should be kept ( true
) or omitted ( false
).sum
. Here always the list flavor is used, but the result type and content is completely determined by the parameterization of the statement by successively aggregating one entry after the other into an arbitrary initial value. Note : The special reference _
is not set for inlined lambda functions as part of the mapping syntax. Therefore the mapping statements (and all other statements using inlined lambda functions as part of their syntax) can be used inside regular lambda functions without hampering the meaning of this special refrence for the surrounding explicit lambda expression.
(( map[list|elem|->dynaml-expr] ))
Execute a mapping expression on members of a list to produce a new (mapped) list. The first expression ( list
) must resolve to a list. The last expression ( x ":" port
) defines the mapping expression used to map all members of the given list. Inside this expression an arbitrarily declared simple reference name (here x
) can be used to access the actually processed list element.
เช่น
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
อัตราผลตอบแทน
port : 4711
hosts :
- alice
- bob
mapped :
- alice:4711
- bob:4711
This expression can be combined with others, for example:
port : 4711
list :
- alice
- bob
joined : (( join( ", ", map[list|x|->x ":" port] ) ))
which magically provides a comma separated list of ported hosts:
port : 4711
list :
- alice
- bob
joined : alice:4711, bob:4711
(( map[list|idx,elem|->dynaml-expr] ))
In this variant, the first argument idx
is provided with the index and the second elem
with the value for the index.
เช่น
list :
- name : alice
age : 25
- name : bob
age : 24
ages : (( map[list|i,p|->i + 1 ". " p.name " is " p.age ] ))
อัตราผลตอบแทน
list :
- name : alice
age : 25
- name : bob
age : 24
ages :
- 1. alice is 25
- 2. bob is 24
(( map[map|key,value|->dynaml-expr] ))
Mapping of a map to a list using a mapping expression. The expression may have access to the key and/or the value. If two references are declared, both values are passed to the expression, the first one is provided with the key and the second one with the value for the key. If one reference is declared, only the value is provided.
เช่น
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
อัตราผลตอบแทน
ages :
alice : 25
bob : 24
keys :
- alice
- bob
(( map{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys and the new entry values. As for a list mapping additionally a key variable can be specified in the variable list.
persons :
alice : 27
bob : 26
older : (( map{persons|x|->x + 1} ) ))
just increments the value of all entries by one in the field older
:
older :
alice : 28
bob : 27
หมายเหตุ
An alternate way to express the same is to use sum[persons|{}|s,k,v|->s { k = v + 1 }]
.
(( map{list|elem|->dynaml-expr} ))
Using {}
instead of []
together with a list in the mapping syntax, the result is again a map with the list elements as key and the mapped entry values. For this all list entries must be strings. As for a list mapping additionally an index variable can be specified in the variable list.
persons :
- alice
- bob
length : (( map{persons|x|->length(x)} ) ))
just creates a map mapping the list entries to their length:
length :
alice : 5
bob : 3
(( select[expr|elem|->dynaml-expr] ))
With select
a map or list can be filtered by evaluating a boolean expression for every entry. An entry is selected if the expression evaluates to true equivalent value. (see conditions).
Basically it offers all the mapping flavors available for map[]
เช่น
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
evaluates selected to
selected :
- name : bob
age : 26
หมายเหตุ
An alternate way to express the same is to use map[list|v|->v.age > 25 ? v :~]
.
(( select{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys filtered by the given expression.
persons :
alice : 25
bob : 26
older : (( select{persons|x|->x > 25} ))
just keeps all entries with a value greater than 25 and omits all others:
selected :
bob : 26
This flavor only works on maps .
หมายเหตุ
An alternate way to express the same is to use sum[persons|{}|s,k,v|->v > 25 ? s {k = v} :s]
.
Aggregations are used to produce a single result from the entries of a list or map aggregating the entries by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the aggregation function: It can be inlined as in (( sum[list|0|s,x|->s + x] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( sum[list|0|aggregation.expression))
(here the aggregation function is taken from the property aggregation.expression
, which should hold an approriate lambda การทำงาน).
(( sum[list|initial|sum,elem|->dynaml-expr] ))
Execute an aggregation expression on members of a list to produce an aggregation result. The first expression ( list
) must resolve to a list. The second expression is used as initial value for the aggregation. The last expression ( s + x
) defines the aggregation expression used to aggregate all members of the given list. Inside this expression an arbitrarily declared simple reference name (here s
) can be used to access the intermediate aggregation result and a second reference name (here x
) can be used to access the actually processed list element.
เช่น
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
อัตราผลตอบแทน
list :
- 1
- 2
sum : 3
(( sum[list|initial|sum,idx,elem|->dynaml-expr] ))
In this variant, the second argument idx
is provided with the index and the third elem
with the value for the index.
เช่น
list :
- 1
- 2
- 3
prod : (( sum[list|0|s,i,x|->s + i * x ] ))
อัตราผลตอบแทน
list :
- 1
- 2
- 3
prod : 8
(( sum[map|initial|sum,key,value|->dynaml-expr] ))
Aggregation of the elements of a map to a single result using an aggregation expression. The expression may have access to the key and/or the value. The first argument is always the intermediate aggregation result. If three references are declared, both values are passed to the expression, the second one is provided with the key and the third one with the value for the key. If two references are declared, only the second one is provided with the value of the map entry.
เช่น
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
อัตราผลตอบแทน
ages :
alice : 25
bob : 24
sum : 49
Projections work over the elements of a list or map yielding a result list. Hereby every element is mapped by an optional subsequent reference expression. This may contain again projections, dynamic references or lambda calls. Basically this is a simplified form of the more general mapping yielding a list working with a lambda function using only a reference expression based on the elements.
(( expr.[*].value ))
All elements of a map or list given by the expression expr
are dereferenced with the subsequent reference expression (here .expr
). If this expression works on a map the elements are ordered accoring to their key values. If the subsequent reference expression is omitted, the complete value list isreturned. For a list expression this means the identity operation.
eg:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[*].name ))
yields for names
:
names :
- alice
- bob
- peter
or for maps:
networks :
ext :
cidr : 10.8.0.0/16
zone1 :
cidr : 10.9.0.0/16
cidrs : (( .networks.[*].cidr ))
yields for cidrs
:
cidrs :
- 10.8.0.0/16
- 10.9.0.0/16
(( list.[1..2].value ))
This projection flavor only works for lists. The projection is done for a dedicated slice of the initial list.
eg:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[1..2].name ))
yields for names
:
names :
- bob
- peter
In argument lists or list literals the list expansion operator ( ...
) can be used. It is a postfix operator on any list expression. It substituted the list expression by a sequence of the list members. It can be be used in combination with static list argument denotation.
eg:
list :
- a
- b
result : (( [ 1, list..., 2, list... ] ))
evaluates result
to
result :
- 1
- a
- b
- 2
- a
- b
The following example demonstrates the usage in combination with the varargs operator in functions:
func : (( |a,b...|-> [a] b ))
list :
- a
- b
a : (( .func(1,2,3) ))
b : (( .func("x",list..., "z") ))
c : (( [ "x", .func(list...)..., "z" ] ))
evaluates the following results:
a :
- 1
- 2
- 3
b :
- x
- a
- b
- z
c :
- x
- a
- b
- z
Please note, that the list expansion might span multiple arguments (including the varargs parameter) in lambda function calls.
Nodes of the yaml document can be marked to enable dedicated behaviours for this node. Such markers are part of the dynaml syntax and may be prepended to any dynaml expression. They are denoted by the &
character directly followed by a marker name. If the expression is a combination of markers and regular expressions, the expression follows the marker list enclosed in brackets (for example (( &temporary( a + b ) ))
).
Note : Instead of using a <<:
insert field to place markers it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( &temporary ))
Maps, lists or simple value nodes can be marked as temporary . Temporary nodes are removed from the final output document, but are available during merging and dynaml evaluation.
eg:
temp :
<< : (( &temporary ))
foo : bar
value : (( temp.foo ))
yields:
value : bar
Adding - <<: (( &temporary ))
to a list can be used to mark a list as temporary.
The temporary marker can be combined with regular dynaml expressions to tag plain fields. Hereby the parenthesised expression is just appended to the marker
eg:
data :
alice : (( &temporary ( "bar" ) ))
foo : (( alice ))
yields:
data :
foo : bar
The temporary marker can be combined with the template marker to omit templates from the final output.
(( &local ))
The marker &local
acts similar to &temporary
but local nodes are always removed from a stub directly after resolving dynaml expressions. Such nodes are therefore not available for merging and they are not used for further merging of stubs and finally the template.
(( &dynamic ))
This marker can be used to mark a template expression (direct or referenced) to enforce the re-evaluation of the template in the usage context whenever the node is used to override or inject a node value along the processing chain. It can also be used together with &inject
or &default
.
eg:
template.yaml
data : 1
merged with
stub.yaml
id : (( &dynamic &inject &template(__ctx.FILE) ))
will resolve to
id : template.yaml
data : 1
The original template is kept along the merge chain and is evaluated separately in the context of the very stub or template it is used.
Using this marker for nodes not evaluationg to a template value is not possible.
(( &inject ))
This marker requests the marked item to be injected into the next stub level, even is the hosting element (list or map) does not requests a merge. This only works if the next level stub already contains the hosting element.
eg:
template.yaml
alice :
foo : 1
stub.yaml
alice :
bar : (( &inject(2) ))
nope : not injected
bob :
<< : (( &inject ))
foobar : yep
is merged to
alice :
foo : 1
bar : 2
bob :
foobar : yep
(( &default ))
Nodes marked as default will be used as default values for downstream stub levels. If no such entry is set there it will behave like &inject
and implicitly add this node, but existing settings will not be overwritten.
Maps (or lists) marked as default will be considered as values. The map is used as a whole as default if no such field is defined downstream.
eg:
template.yaml
data : { }
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
is merged to
data :
foobar :
foo : claude
bar : peter
Their entries will neither be used for overwriting existing downstream values nor for defaulting non-existng fields of a not defaulted map field.
eg:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
is merged to
data :
foobar :
bar : bob
If sub sequent defaulting is desired, the fields of a default map must again be marked as default.
eg:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : (( &default ("claude") ))
bar : peter
is merged to
data :
foobar :
foo : claude
bar : bob
Note : The behaviour of list entries marked as default is undefined.
(( &state ))
Nodes marked as state are handled during the merge processing as if the marker would not be present. But there will be a special handling for enabled state processing (option --state <path>
) at the end of the template processing. Additionally to the regular output a document consisting only of state nodes (plus all nested nodes) will be written to a state file. This file will be used as top-level stub for further merge processings with enabled state support.
This enables to keep state between two merge processings. For regular merging sich nodes are only processed during the first processing. Later processings will keep the state from the first one, because those nodes will be overiden by the state stub added to the end of the sub list.
If those nodes additionally disable merging (for example using (( &state(merge none) ))
) dynaml expressions in sub level nodes may perform explicit merging using the function stub()
to refer to values provided by already processed stubs (especially the implicitly added state stub). For an example please refer to the state library.
(( &template ))
Nodes marked as template will not be evaluated at the place of their occurrence. Instead, they will result in a template value stored as value for the node. They can later be instantiated inside a dynaml expression (see below).
(( &tag:name ))
The tag marker can be used to assign a logical name to a node value. This name can then be used in tagged reference expressions to refer to this node value (see below).
A tagged reference has the form <tagname>::<path>
. The <path>
may denote any sub node of a tagged node. If the value of a complete node (or a simple value node) should be used, the <path>
must denote the root path ( .
).
Tags can be used to label node values in multi-document streams (used as template). After defined for a document the tag can then be used to reference node values from the actual or previous document(s) of a document sequence in a multi-document stream. Tags can be added for complex or simple value nodes. A tagged reference may be used to refer to the tagged value as a whole or sub structure.
(( &tag:name(value) ))
This syntax is used to tag a node whose value is defined by a dynaml expression. It can also be used to denote tagged simple value nodes. (As usual the value part is optional for adding markers to structured values (see Markers).)
eg:
template.yaml
data :
<< : (( &tag:persons ))
alice : (( &tag:alice(25)
If the name is prefixed with a star ( *
), the tag is defined globally. Gobal tags surive stub processing and their value is visible in subsequent stub (and template) processings.
A tag name may consist of multiple components separated by a colon ( :
).
Tags can also be defined dynamically by the dynaml function tagdef.
(( tag::foo ))
Reference a sub path of the value of a tagged node.
eg:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
resolves tagref
to 25
(( tag::. ))
Reference the whole (structured) value of tagged node.
eg:
template.yaml
data :
alice : (( &tag:alice(25) ))
tagref : (( alice::. ))
resolves tagref
to 25
(( foo.bar::alice ))
Tag names may be structured. A tag name consists of a non-empty list of tag components separated by a dot or colon ( :
). A tag component may contain ASCII letters or numbers, starting wit a letter. Multi-component tags are subject to Tag Resolution.
A tag reference always contains a tag name and a path separated by a double colon ( ::
). The standard use-case is to describe a dedicated sub node for a tagged node value.
for example, if the tag X
describes the value
data :
alice : 25
bob : 24
the tagged reference X::data.alice
describes the value 25
.
For tagged references with a path other than .
(the whole tag value), structured tags feature a more sophisticated resolution mechanism. A structured tag consist of multiple tag components separated by a colon ( :
), for example lib:mylib
. Therefore, tags span a tree of namespaces or scopes used to resolve path references. A tag-less reference just uses the actual document or binding to resolve a path expression.
Evaluation of a path reference for a tag tries to resolve the path in the first tag tree level where the path is available (breadth-first search). If this level contains multiple tags that could resolve the given path, the resolution fails because it cannot be unambigiously resolved.
ตัวอย่างเช่น:
tags :
- << : (( &tag:lib:alice ))
data : alice.alice
- << : (( &tag:lib:alice:v1))
data : alice.v1
- << : (( &tag:lib:bob))
other : bob
usage :
data : (( lib::data ))
effectively resolves usage.data
to lib:alice::data
and therefore to the value alice.alice
.
To achieve this all matching sub tags are orderd by their number of tag components. The first sub-level tag containing such a given path is selected. For this level, the matching tag must be non-ambigious. There must only be one tag with this level containing a matching path. If there are multiple ones the evaluation fails. In the above example this would be the case if tag lib:bob
would contain a field data
instead of or additional to other
.
This feature can be used in library stubs to provide qualified names for their elements that can be used with merging the containing document nodes into the template.
If the template file is a multi-document stream the tags are preserved during the complete processing. This means tags defined in a earlier document can be used in all following documents, also. But the tag names must be unique across all documents in a multi-document stream.
eg:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
---
bob : (( persons::bob ))
resolves to
---
alice : 25
---
bob : 24
Tags defined by tag markers are available for stubs and templates. Global tags are available down the stub processing to the templates. Local tags are only avaialble on the processing level they are declared.
Additionally to the tags explicitly set by tag markers, there are implicit document tags given by the document index during the processing of a (multi-document) template. The implicit document tags are qualified with the prefix doc.
- This prefix should not be used to own tags in the documents
eg:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
prev : (( doc.1::. ))
---
bob : (( persons::bob ))
prev : (( doc.2::. ))
resolves to
---
alice : 25
prev :
data :
alice : 25
bob : 24
---
bob : 24
prev :
alice : 25
prev :
data :
alice : 25
bob : 24
If the given document index is negative it denotes the document relative to the one actually processed (so, the tag doc.-1
denotes the previous document). The index doc.0
can be used to denote the actual document. Here always a path must be specified, it is not possible to refer to the complete document (with .
).
A map can be tagged by a dynaml expression to be used as template. Dynaml expressions in a template are not evaluated at its definition location in the document, but can be inserted at other locations using dynaml. At every usage location it is evaluated separately.
<<: (( &template ))
The dynaml expression &template
can be used to tag a map node as template:
eg:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
The template will be the value of the node foo.bar
. As such it can be overwritten as a whole by settings in a stub during the merge process. Dynaml expressions in the template are not evaluated. A map can have only a single <<
field. Therefore it is possible to combine the template marker with an expression just by adding the expression in parenthesis.
Adding - <<: (( &template ))
to a list it is also possible to define list templates. It is also possible to convert a single expression value into a simple template by adding the template marker to the expression, for example foo: (( &template (expression) ))
The template marker can be combined with the temporary marker to omit templates from the final output.
Note : Instead of using a <<:
insert field to place the template marker it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( *foo.bar ))
The dynaml expression *<reference expression>
can be used to evaluate a template somewhere in the yaml document. Dynaml expressions in the template are evaluated in the context of this expression.
eg:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst : (( *foo.bar ))
verb : loves
verb : hates
evaluates to
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst :
alice : alice
bob : loves alice
verb : loves
verb : hates
_
The special reference _
( self ) can be used inside of lambda functions and templates . They refer to the containing element (the lambda function or template).
Additionally it can be used to lookup relative reference expressions starting with the defining document scope of the element skipping intermediate scopes.
eg:
node :
data :
scope : data
funcs :
a : (( |x|->scope ))
b : (( |x|->_.scope ))
c : (( |x|->_.data.scope ))
scope : funcs
call :
scope : call
a : (( node.funcs.a(1) ))
b : (( node.funcs.b(1) ))
c : (( node.funcs.c(1) ))
evaluates call
to
call :
a : call
b : funcs
c : data
scope : call
__
The special reference __
can be used to lookup references as relative references starting with the document node hosting the actually evaluated dynaml expression skipping intermediate scopes.
This can, for example be used to relatively access a lambda value field besides the actual field in a map. The usage of plain function names is reserved for builtin functions and are not used as relative references.
This special reference is also available in expressions in templates and refer to the map node in the template hosting the actually evaluated expression.
eg:
templates :
templ :
<< : (( &template ))
self : (( _ ))
value : (( ($self="value") __.self ))
result : (( scope ))
templ : (( _.scope ))
scope : templates
result :
inst : (( *templates.templ ))
scope : result
evaluates result
to
result :
inst :
result : result
templ : templates
self :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
value :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
scope : result
or with referencing upper nodes:
templates :
templ :
<< : (( &template ))
alice : root
data :
foo : (( ($bob="local") __.bob ))
bar : (( ($alice="local") __.alice ))
bob : static
result : (( *templates.templ ))
evaluates result
to
result :
alice : root
data :
bar : root
foo : static
bob : static
___
The special reference ___
can be used to lookup references in the outer most scope. It can therefore be used to access processing bindings specified for a document processing via command line or API. If no bindings are specified the document root is used.
Calling spiff merge template.yaml --bindings bindings.yaml
with a binding of
bindings.yaml
input1 : binding1
input2 : binding2
and the template
template.yaml
input1 : top1
map :
input : map
input1 : map1
results :
frommap : (( input1 ))
fromroot : (( .input1 ))
frombinding1 : (( ___.input1 ))
frombinding2 : (( input2 ))
evaluates map.results
to
results :
frombinding1 : binding1
frombinding2 : binding2
frommap : map1
fromroot : top1
__ctx.OUTER
The context field OUTER
is used for nested merges. It is a list of documents, index 0 is the next outer document, and so on.
(( {} ))
Provides an empty map.
(( [] ))
Provides an empty list. Basically this is not a dedicated literal, but just a regular list expression without a value.
(( ~ ))
Provides the null value.
(( ~~ ))
This literal evaluates to an undefined expression. The element (list entry or map field) carrying this value, although defined, will be removed from the document and handled as undefined for further merges and the evaluation of referential expressions.
eg:
foo : (( ~~ ))
bob : (( foo || ~~ ))
alice : (( bob || "default"))
evaluates to
alice : default
Inside every dynaml expression a virtual field __ctx
is available. It allows access to information about the actual evaluation context. It can be accessed by a relative reference expression.
The following fields are supported:
Field Name | พิมพ์ | ความหมาย |
---|---|---|
VERSION | เชือก | current version of spiff |
FILE | เชือก | name of actually processed template file |
DIR | เชือก | name of directory of actually processed template file |
RESOLVED_FILE | เชือก | name of actually processed template file with resolved symbolic links |
RESOLVED_DIR | เชือก | name of directory of actually processed template file with resolved symbolic links |
PATHNAME | เชือก | path name of actually processed field |
PATH | list[string] | path name as component list |
OUTER | yaml doc | outer documents for nested merges, index 0 is the next outer document |
BINDINGS | yaml doc | the external bindings for the actual processing (see also ___) |
If external bindings are specified they are the last elements in OUTER
.
eg:
template.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
evaluates to
eg:
foo :
bar :
dir : .
file : template.yml
path :
- foo
- bar
- path
str : foo.bar.str
Dynaml expressions are evaluated obeying certain priority levels. This means operations with a higher priority are evaluated first. For example the expression 1 + 2 * 3
is evaluated in the order 1 + ( 2 * 3 )
. Operations with the same priority are evaluated from left to right (in contrast to version 1.0.7). This means the expression 6 - 3 - 2
is evaluated as ( 6 - 3 ) - 2
.
The following levels are supported (from low priority to high priority)
||
, //
foo bar
)-or
, -and
==
, !=
, <=
, <
, >
, >=
+
, -
*
, /
, %
( )
, !
, constants, references ( foo.bar
), merge
, auto
, lambda
, map[]
, and functionsThe complete grammar can be found in dynaml.peg.
Feature state: alpha
Attention: This is an alpha feature. It must be enabled on the command line with the --interpolation
or --features=interpolation
option. Also for the spiff library it must explicitly be enabled. By adding the key interpolation
to the feature list stored in the environment variable SPIFF_FEATURES
this feature will be enabled by default.
Typically a complete value can either be a literal or a dynaml expression. For string literals it is possible to use an interpolation syntax to embed dynaml expressions into strings.
ตัวอย่างเช่น
data : test
interpolation : this is a (( data ))
replaces the part between the double brackets by the result of the described expression evaluation. Here the brackets can be escaped by the usual escaping ( ((!
) syntax.
Those string literals will implicitly be converted to complete flat dynaml expressions. The example above will therefore be converted into
(( "this is a " data ))
which is the regular dynaml equivalent. The escaping is very ticky, and may be there are still problems. Quotes inside an embedded dynaml expression can be escaped to enable quotes in string literals.
Incomplete or partial interpolation expressions will be ignored and just used as string.
Strings inside a dynaml expression are NOT directly interpolated again, thus
data : " test "
interpolated : " this is a (( length( " (( data )) " ) data )) "
will resolve interpolation
to this is 10test
and not to this is 4test
.
But if the final string after the expression evaluation again describes a string interpolation it will be processed, again.
data : test
interpolation : this is a (( "(( data ))" data ))
will resolve interpolation
to this is testtest
.
The embedded dynaml expression must be concatenatable with strings.
Feature state: alpha
In addition to describe conditions and loops with dynaml expressions it is also possible to use elements of the document structure to embed control structures.
Such a YAML-based control structure is always described as a map in YAML/JSON. The syntactical elements are expressed as map fields starting with <<
. Additionally, depending on the control structure, regular fields are possible. Control structures finally represent a value for the containing node. They may contain marker expressions ( <<
), also.
eg:
temp :
<< : (( &temporary ))
<<if : (( features("control") ))
<<then :
alice : 25
bob : 26
resolves to
final :
alice : 25
bob : 26
Tu use this alpha feature the feature flag control
must be enabled.
Please be aware: Control structure maps typically are always completely resolved before they are evaluated.
A control structure itself is always a dedicated map node in a document. It is substituted by a regular value node determined by the execution of the control structure.
The fields of a control structure map are not subject to overwriting by stubs, but the complete structure can be overwritten.
If used as value for a map field the resulting value is just used as effective value for this field.
If a map should be enriched by maps resulting from multiple control structures the special control structure <<merge:
can be used. It allows to specify a list of maps which should be merged with the actual control structure map to finally build the result value.
A control structure can be used as list value, also. In this case there is a dedicated interpretation of the resulting value of the control structure. If it is NOT a list value, for convenience, the value is directly used as list entry and substitutes the control structure map.
If the resulting value is again a list, it is inserted into the containing list at the place of occurrence of the control structure. So, if a list value should be used as dedicated entry in a list, the result of a control structure must be a list with the intended list as entry.
eg:
list :
- <<if : (( features("control") ))
<<then : alice
- <<if : (( features("control") ))
<<then :
- - peter
- <<if : (( features("control") ))
<<then :
- bob
resolves to
list :
- alice
- - peter
- bob
<<if:
The condition structure is defined by the syntax field <<if
. It additionally accepts the fields <<then
and <<else
.
The condition field must provide a boolean value. If it is true
the optional <<then
field is used to substitute the control structure, otherwise the optional <<else
field is used.
If the appropriate case is not specified, the result is the undefined (( ~~ ))
value. The containing field is therefore completely omitted from the output.
.eg:
x : test1
cond :
field :
<<if : (( x == "test" ))
<<then : alice
<<else : bob
evaluates cond.field
to bob
If the else case is omitted, the cond
field would be an empty map ( field
is omitted, because the contained control structure evaluates to undefined )
A comparable way to do this with regular dynaml could look like this:
cond : (( x == "test" ? "alice" :"bob" ))
A better way more suitable for complex cases would be:
local :
<< : (( &local))
then : alice
else : bob
cond : (( x == "test" ? local.then :local.else ))
<<switch:
The switch
control structure evaluates the switch value of the <<switch
field to a string and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
The nil value matches the default
case. If the switch value is undefined the control evaluates to the undefined value (( ~~ ))
.
eg:
x : alice
value :
<<switch : (( x ))
alice : 25
bob : 26
<<default : other
evaluates value
to 25
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
alice : 25
bob : 26
default : other
value : (( local.cases[x] || local.default ))
<<type:
The type
control structure evaluates the type of the value of the <<type
field and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
eg:
x : alice
value :
<<type : (( x ))
string : alice
<<default : unknown
evaluates value
to alice
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
string : alice
default : unknown
value : (( local.cases[type(x)] || local.default ))
For more complex scenarios not only switching on strings a second syntax can be used. Instead of using fields in the control map as cases, a dedicated field <<cases
may contain a list of cases, that are checked sequentially (In this flavor regular fields are not allowed anymore).
Every case is described again by a map containing the fields:
case
: the expected value to match the switch valuematch
: a lambda function taking one argument and yielding a boolean value used to match the given switch valuevalue
: (optional) the resulting value in case of a match. If not defined the result will be the undefined value. One of case
or match
must be present.
eg:
x : 5
selected :
<<switch : (( x ))
<<cases :
- case :
alice : 25
value : alice
- match : (( |v|->v == 5 ))
value : bob
<<default : unknown
resolves to
x : 5
selected : bob
If x
would be set to the complex value
x :
alice : 25
it would resolve to
x :
alice : 25
selected : alice
<<for:
The loop control is able to execute a multi-dimensional loop and produce a list or map based on the value combinations.
The loop ranges are specified by the value of the <<for
field. It is possible ol loop over lists or maps. The range specification can be given by either a map or list:
map : the keys of the map are the names of the control variables and the values must be lists or maps specifying the ranges.
The map key might optionally be a comma-separated pair (for example key,value
) of variable names. In this case the first name is the name for the index variable and the second one for the value variable.
If multiple ranges are specified iterations are alphabetically ordered by value variable name (first) and index variable name (second) to determine the traversing order.
list : if the control variables are defined by a list, each list element must contain two mandatory and one optional field(s):
name
: the name of the (list or map entry value) control variablevalues
: a list to define the value range.index
: (optional) the name of the variable providing the list index or map key of the loop range (defaulted to index-<name>
)Here the order in the list determine the traversal order.
Traversal is done by recursively iterating follow up ranges for every entry in the actual range. This means the last range is completely iterated for the first values of the first ranges first.
If no index variable is specified for a loop range there is an additional implicit binding for every control variable describing the actual list index or map key of the processed value for this dimension. It is denoted by index-<control variable>
If multiple loop ranges are specified, the ranges may mix iterations over maps and lists.
The iteration result value is determined by the value of the <<do
field. It is implicitly handled as template and is evaluated for every set of iteration values.
The result of the evaluation using only the <<do
value field is a list.
eg:
alice :
- a
- b
bob :
- 1
- 2
- 3
list :
<<for :
key,alice : (( .alice )) # sorted by using alice as primary sort key
bob : (( .bob ))
<<do :
value : (( alice "-" key "-" bob "-" index-bob ))
evaluates list
to
list :
- value : a-0-1-0
- value : a-0-2-1
- value : a-0-3-2
- value : b-1-1-0
- value : b-1-2-1
- value : b-1-3-2
It first iterates over the values for alice
. For each such value it then iterates over the values of bob
.
A comparable way to do this with regular dynaml could look like this:
list : (( sum[alice|[]|s,key,alice|-> s sum[bob|[]|s,index_bob,bob|->s (alice "-" key "-" bob "-" index_bob)]] ))
A result list may omit entries if the value expression evaluates to the undefined value ( ~~
). The nil value ( ~
) is kept. This way a for
control can be used to filter lists.
eg:
bob :
- 1
- 2
- 3
filtered :
<<for :
bob : (( .bob ))
<<do : (( bob == 2 ? ~~ :bob ))
resolves to
bob :
- 1
- 2
- 3
filtered :
- 1
- 3
If the result should be a map it is required to additionally specify a key value for every iteration. This is specified by the optional <<mapkey
field. Like the <<do
field it is implicitly handled as template and re-evaluated for every iteration.
eg:
x : suffix
alice :
- a
- b
bob :
- 1
- 2
- 3
map :
<<for :
- name : alice
values : (( .alice ))
- name : bob
values : (( .bob ))
<<mapkey : (( alice bob ))
<<do :
value : (( alice bob x ))
evaluates the field map
to
map :
a1 :
value : a1suffix
a2 :
value : a2suffix
a3 :
value : a3suffix
b1 :
value : b1suffix
b2 :
value : b2suffix
b3 :
value : b3suffix
Here the traversal order is irrelevant as long as the generated key values are unique. If several evaluations of the key expression yield the same value the last one will win.
A comparable way to do this with regular dynaml could look like this:
map : (( sum[alice|{}|s,index_alice,alice|-> s sum[bob|{}|s,index_bob,bob|->s {(alice bob)=alice bob x}]] ))
An iteration value is ignored if the key or the value evaluate to the undefined value (( ~~ ))
. Additionally the key may evaluate to the nil value (( ~ ))
, also.
eg:
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( key ))
<<do : (( bob == 2 ? ~~ :bob ))
หรือ
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( bob == 2 ? ~~ :key ))
<<do : (( bob ))
resolve to
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
b1 : 1
b3 : 3
<<merge:
With merge
it is possible to merge maps given as list value of the <<merge
field with regular map fields from the control structure to determine the final map value.
The value for <<merge:
may be a single map or a list of maps to join with the directly given fields.
eg:
map :
<<merge :
- bob : 26
charlie : 1
- charlie : 27
alice : 25
charlie : 2
resolves to
map :
alice : 25
bob : 26
charlie : 27
If multiple maps contain the same key, the last value (in order of list) will win.
This might be combined with other control structures, for example to conditionally merge multiple maps:
eg:
x : charlie
map :
<<merge :
- <<if : (( x == "charlie" ))
<<then :
charlie : 27
- <<if : (( x == "alice" ))
<<then :
alice : 20
alice : 25
charlie : 2
resolves to
x : charlie
map :
alice : 25
charlie : 27
By default spiff
performs a deep structural merge of its first argument, the template file, with the given stub files. The merge is processed from right to left, providing an intermediate merged stub for every step. This means, that for every step all expressions must be locally resolvable.
Structural merge means, that besides explicit dynaml merge
expressions, values will be overridden by values of equivalent nodes found in right-most stub files. In general, flat value lists are not merged. Only lists of maps can be merged by entries in a stub with a matching index.
There is a special support for the auto-merge of lists containing maps, if the maps contain a name
field. Hereby the list is handled like a map with entries according to the value of the list entries' name
field. If another key field than name
should be used, the key field of one list entry can be tagged with the prefix key:
to indicate the indended key name. Such tags will be removed for the processed output.
In general the resolution of matching nodes in stubs is done using the same rules that apply for the reference expressions (( foo.bar.[1].baz )).
For example, given the file template.yml :
foo :
- name : alice
bar : template
- name : bob
bar : template
plip :
- id : 1
plop : template
- id : 2
plop : template
bar :
- foo : template
list :
- a
- b
and file stub.yml :
foo :
- name : bob
bar : stub
plip :
- key:id : 1
plop : stub
bar :
- foo : stub
list :
- c
- d
spiff merge template.yml stub.yml
ผลตอบแทน
foo :
- bar : template
name : alice
- bar : stub
name : bob
plip :
- id : 1
plop : stub
- id : 2
plop : template
bar :
- foo : stub
list :
- a
- b
Be careful that any name:
key in the template for the first element of the plip
list will defeat the key:id: 1
selector from the stub. When a name
field exist in a list element, then this element can only be targeted by this name. When the selector is defeated, the resulting value is the one provided by the template.
Merging the following files in the given order
deployment.yml
networks : (( merge ))
cf.yml
utils : (( merge ))
network : (( merge ))
meta : (( merge ))
networks :
- name : cf1
<< : (( utils.defNet(network.base.z1,meta.deployment_no,30) ))
- name : cf2
<< : (( utils.defNet(network.base.z2,meta.deployment_no,30) ))
infrastructure.yml
network :
size : 16
block_size : 256
base :
z1 : 10.0.0.0
z2 : 10.1.0.0
rules.yml
utils :
defNet : (( |b,n,s|->(*.utils.network).net ))
network :
<< : (( &template ))
start : (( b + n * .network.block_size ))
first : (( start + ( n == 0 ? 2 :0 ) ))
lower : (( n == 0 ? [] :b " - " start - 1 ))
upper : (( start + .network.block_size " - " max_ip(net.subnets.[0].range) ))
net :
subnets :
- range : (( b "/" .network.size ))
reserved : (( [] lower upper ))
static :
- (( first " - " first + s - 1 ))
instance.yml
meta :
deployment_no : 1
will yield a network setting for a dedicated deployment
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0 - 10.0.255.255
static :
- 10.0.1.0 - 10.0.1.29
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.0.0 - 10.1.0.255
- 10.1.2.0 - 10.1.255.255
static :
- 10.1.1.0 - 10.1.1.29
Using the same config for another deployment of the same type just requires the replacement of the instance.yml
. Using a different instance.yml
meta :
deployment_no : 0
will yield a network setting for a second deployment providing the appropriate settings for a unique other IP block.
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.1.0 - 10.0.255.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.1.0 - 10.1.255.255
static :
- 10.1.0.2 - 10.1.0.31
If you move to another infrastructure you might want to change the basic IP layout. You can do it just by adapting the infrastructure.yml
network :
size : 17
block_size : 128
base :
z1 : 10.0.0.0
z2 : 10.0.128.0
Without any change to your other settings you'll get
networks :
- name : cf1
subnets :
- range : 10.0.0.0/17
reserved :
- 10.0.0.128 - 10.0.127.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.0.128.0/17
reserved :
- 10.0.128.128 - 10.0.255.255
static :
- 10.0.128.2 - 10.0.128.31
There are several scenarios yielding results that do not seem to be obvious. Here are some typical pitfalls.
The auto merge never adds nodes to existing structures
For example, merging
template.yml
foo :
alice : 25
กับ
stub.yml
foo :
alice : 24
bob : 26
อัตราผลตอบแทน
foo :
alice : 24
Use <<: (( merge )) to change this behaviour, or explicitly add desired nodes to be merged:
template.yml
foo :
alice : 25
bob : (( merge ))
Simple node values are replaced by values or complete structures coming from stubs, structures are deep merged.
For example, merging
template.yml
foo : (( ["alice"] ))
กับ
stub.yml
foo :
- peter
- paul
อัตราผลตอบแทน
foo :
- peter
- paul
But the template
foo : [ (( "alice" )) ]
is merged without any change.
Expressions are subject to be overridden as a whole
A consequence of the behaviour described above is that nodes described by an expession are basically overridden by a complete merged structure, instead of doing a deep merge with the structues resulting from the expression evaluation.
For example, merging
template.yml
men :
- bob : 24
women :
- alice : 25
people : (( women men ))
กับ
stub.yml
people :
- alice : 13
อัตราผลตอบแทน
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
To request an auto-merge of the structure resulting from the expression evaluation, the expression has to be preceeded with the modifier prefer
( (( prefer women men ))
). This would yield the desired result:
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
- bob : 24
Nested merge expressions use implied redirections
merge
expressions implicity use a redirection implied by an outer redirecting merge. In the following example
meta :
<< : (( merge deployments.cf ))
properties :
<< : (( merge ))
alice : 42
the merge expression in meta.properties
is implicity redirected to the path deployments.cf.properties
implied by the outer redirecting merge
. Therefore merging with
deployments :
cf :
properties :
alice : 24
bob : 42
อัตราผลตอบแทน
meta :
properties :
alice : 24
bob : 42
Functions and mappings can freely be nested
eg:
pot : (( lambda |x,y|-> y == 0 ? 1 :(|m|->m * m)(_(x, y / 2)) * ( 1 + ( y % 2 ) * ( x - 1 ) ) ))
seq : (( lambda |b,l|->map[l|x|-> .pot(b,x)] ))
values : (( .seq(2,[ 0..4 ]) ))
yields the list [ 1,2,4,8,16 ]
for the property values
.
Functions can be used to parameterize templates
The combination of functions with templates can be use to provide functions yielding complex structures. The parameters of a function are part of the scope used to resolve reference expressions in a template used in the function body.
eg:
relation :
template :
<< : (( &template ))
bob : (( x " " y ))
relate : (( |x,y|->*relation.template ))
banda : (( relation.relate("loves","alice") ))
evaluates to
relation :
relate : lambda|x,y|->*(relation.template)
template :
<< : (( &template ))
bob : (( x " " y ))
banda :
bob : loves alice
Scopes can be used to parameterize templates
Scope literals are also considered when instantiating templates. Therefore they can be used to set explicit values for relative reference expressions used in templates.
eg:
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped : (( ( $alice = 25, "bob" = 26 ) *template ))
evaluates to
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped :
sum : 51
Aggregations may yield complex values by using templates
The expression of an aggregation may return complex values by returning inline lists or instantiated templates. The binding of the function will be available (as usual) for the evaluation of the template. In the example below the aggregation provides a map with both the sum and the product of the list entries containing the integers from 1 to 4.
eg:
sum : (( sum[[1..4]|init|s,e|->*temp] ))
temp :
<< : (( &template ))
sum : (( s.sum + e ))
prd : (( s.prd * e ))
init :
sum : 0
prd : 1
yields for sum
the value
sum:
prd: 24
sum: 10
Taking advantage of the undefined value
At first glance it might look strange to introduce a value for undefined . But it can be really useful as will become apparent with the following examples.
Whenever a stub syntactically defines a field it overwrites the default in the template during merging. Therefore it would not be possible to define some expression for that field that eventually keeps the default value. Here the undefined value can help:
eg: merging
template.yml
alice : 24
bob : 25
กับ
stub.yml
alice : (( config.alice * 2 || ~ ))
bob : (( config.bob * 3 || ~~ ))
อัตราผลตอบแทน
alice : ~
bob : 25
There is a problem accessing upstream values. This is only possible if the local stub contains the definition of the field to use. But then there will always be a value for this field, even if the upstream does not overwrite it.
Here the undefined value can help by providing optional access to upstream values. Optional means, that the field is only defined, if there is an upstream value. Otherwise it is undefined for the expressions in the local stub and potential downstream templates. This is possible because the field is formally defined, and will therefore be merged, only after evaluating the expression if it is not merged it will be removed again.
eg: merging
template.yml
alice : 24
bob : 25
peter : 26
กับ
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
และ
config.yml
config :
alice : 4711
peter : 0815
อัตราผลตอบแทน
alice : 4711 # transferred from config's config value
bob : 25 # kept default value, because not set in config.yml
peter : 26 # kept, because mapping source not available in mapping.yml
This can be used to add an intermediate stub, that offers a dedicated configuration interface and contains logic to map this interface to a manifest structure already defining default values.
Templates versus map literals
As described earlier templates can be used inside functions and mappings to easily describe complex data structures based on expressions refering to parameters. Before the introduction of map literals this was the only way to achieve such behaviour. The advantage is the possibility to describe the complex structure as regular part of a yaml document, which allows using the regular yaml formatting facilitating readability.
eg:
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ *templates.job ] ] ))
templates :
job :
<< : (( &template ))
name : (( k ))
instances : (( v ))
evaluates to
scaling :
runner_z1 : 10
router_z1 : 4
jobs :
- instances : 4
name : router_z1
- instances : 10
name : runner_z1
...
With map literals this construct can significantly be simplified
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ {"name"=k, "value"=v} ] ] ))
Nevertheless the first, template based version might still be useful, if the data structures are more complex, deeper or with complex value expressions. For such a scenario the description of the data structure as template should be preferred. It provides a much better readability, because every field, list entry and value expression can be put into dedicated lines.
But there is still a qualitative difference. While map literals are part of a single expression always evaluated as a whole before map fields are available for referencing, templates are evaluated as regular yaml documents that might contain multiple fields with separate expressions referencing each other.
eg:
range : (( (|cidr,first,size|->(*templates.addr).range)("10.0.0.0/16",10,255) ))
templates :
addr :
<< : (( &template ))
base : (( min_ip(cidr) ))
start : (( base + first ))
end : (( start + size - 1 ))
range : (( start " - " end ))
evaluates range
to
range : 10.0.0.10 - 10.0.1.8
...
Defaulting and Requiring Fields
Traditionally defaulting in spiff is done by a downstream template where the playload data file is used as stub.
Fields with simple values can just be specified with their values. They will be overwritten by stubs using the regular spiff document merging mechanisms.
It is more difficult for maps or lists. If a map is specified in the template only its fields will be merged (see above), but it is never replaced as a whole by settings in the playload definition files. And Lists are never merged.
Therefore maps and lists that should be defaulted as a whole must be specified as initial expressions (referential or inline) in the template file.
eg: merging of
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
และ
payload.yaml
config :
value2 : configured
othervalue : I want this but don't get it
evaluates to
config :
person :
age : bob
name : alice
value1 : defaultvalue
value2 : configured
In such a scenario the structure of the resulting document is defined by the template. All kinds of variable fields or sub-structures must be forseen by the template by using <<: (( merge ))
expressions in maps.
eg: changing template to
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
<< : (( merge ))
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
Known optional fields can be described using the undefined ( ~~
) expression:
template.yaml
config :
optional : (( ~~ ))
Such fields will only be part of the final document if they are defined in an upstream stub, otherwise they will be completely removed.
Required fields can be defined with the expression (( merge ))
. If no stub contains a value for this field, the merge cannot be fullfilled and an error is reported. If a dedicated message should be shown instead, the merge expression can be defaulted with an error function call.
eg:
template.yaml
config :
password : (( merge || error("the field password is required") ))
will produce the following error if no stub contains a value:
error generating manifest: unresolved nodes:
(( merge || error("the field password is required") )) in c.yaml config.password () *the field password is required
This can be simplified by reducing the expression to the sole error
expression.
Besides this template based defaulting it is also possible to provide defaults by upstream stubs using the &default
marker. Here the payload can be a downstream file.
X509 and providing State
When generating keys or certificates with the X509 Functions there will be new keys or certificates for every execution of spiff . But it is also possible to use spiff to maintain key state. A very simple script could look like this:
#! /bin/bash
DIR= " $( dirname " $0 " ) /state "
if [ ! -f " $DIR /state.yaml " ] ; then
echo " state: " > " $DIR /state.yaml "
fi
spiff merge " $DIR /template.yaml " " $DIR /state.yaml " > " $DIR /. $$ " && mv " $DIR /. $$ " " $DIR /state.yaml "
It uses a template file (containing the rules) and a state file with the actual state as stub. The first time it is executed there is an empty state and the rules are not overridden, therefore the keys and certificates are generated. Later on, only additional new fields are calculated, the state fields already containing values just overrule the dynaml expressions for those fields in the template.
If a re-generation is required, the state file can just be deleted.
A template may look like this:
state/template.yaml
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : rootca
privateKey : (( state.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
peer :
organization : Mandelsoft
commonName : etcd
publicKey : (( state.pub ))
caCert : (( state.cacert ))
caPrivateKey : (( state.cakey ))
validity : 100
usage :
- ServerAuth
- ClientAuth
- KeyEncipherment
hosts :
- etcd.mandelsoft.org
state :
cakey : (( x509genkey(2048) ))
capub : (( x509publickey(cakey) ))
cacert : (( x509cert(spec.ca) ))
key : (( x509genkey(2048) ))
pub : (( x509publickey(key) ))
peer : (( x509cert(spec.peer) ))
The merge then generates a rootca and some TLS certificate signed with this CA.
Generating, Deploying and Accessing Status for Kubernetes Resources
The sync
function offers the possibility to synchronize the template processing with external content. This can also be the output of a command execution. Therefore the template processing can not only be used to generate a deployment manifest, but also for applying this to a target system and retrieving deployment status values for the further processing.
A typical scenario of this kind could be a kubernetes setup including a service of type LoadBalancer . Once deployed it gets assigned status information about the IP address or hostname of the assigned load balancer. This information might be required for some other deployment manifest.
A simple template for such a deployment could like this:
service :
apiVersion : v1
kind : Service
metadata :
annotations :
dns.mandelsoft.org/dnsnames : echo.test.garden.mandelsoft.org
dns.mandelsoft.org/ttl : " 500 "
name : test-service
namespace : default
spec :
ports :
- name : http
port : 80
protocol : TCP
targetPort : 8080
sessionAffinity : None
type : LoadBalancer
deployment :
testservice : (( sync[pipe_uncached(service, "kubectl", "apply", "-f", "-", "-o", "yaml")|value|->defined(value.status.loadBalancer.ingress)] ))
otherconfig :
lb : (( deployment.testservice.status.loadBalancer.ingress ))
Crazy Shit: Graph Analaysis with spiff
It is easy to describe a simple graph with knots and edges (for example for a set of components and their dependencies) just by using a map of lists.
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
Now it would be useful to figure out whether there are dependency cycles or to determine ordered transitive dependencies for a component.
Let's say something like this:
graph :
utilities :
closures : (( utilities.graph.evaluate(graph) ))
cycles : (( utilities.graph.cycles(closures) ))
Indeed, this can be done with spiff. The only thing required is a "small utilities stub" .
utilities :
<< : (( &temporary ))
graph :
_dep : (( |model,comp,closure|->contains(closure,comp) ? { $deps=[], $err=closure [comp]} :($deps=_._deps(model,comp,closure [comp]))($err=sum[deps|[]|s,e|-> length(s) >= length(e.err) ? s :e.err]) { $deps=_.join(map[deps|e|->e.deps]), $err=err} ))
_deps : (( |model,comp,closure|->map[model.[comp]|dep|->($deps=_._dep(model,dep,closure)) { $deps=[dep] deps.deps, $err=deps.err }] ))
join : (( |lists|->sum[lists|[]|s,e|-> s e] ))
min : (( |list|->sum[list|~|s,e|-> s ? e < s ? e :s :e] ))
normcycle : (( |cycle|->($min=_.min(cycle)) min ? sum[cycle|cycle|s,e|->s.[0] == min ? s :(s.[1..] [s.[1]])] :cycle ))
cycle : (( |list|->list ? ($elem=list.[length(list) - 1]) _.normcycle(sum[list|[]|s,e|->s ? s [e] :e == elem ? [e] :s]) :list ))
norm : (( |deps|->{ $deps=_.reverse(uniq(_.reverse(deps.deps))), $err=_.cycle(deps.err) } ))
reverse : (( |list|->sum[list|[]|s,e|->[e] s] ))
evaluate : (( |model|->sum[model|{}|s,k,v|->s { k=_.norm(_._dep(model,k,[]))}] ))
cycles : (( |result|->uniq(sum[result|[]|s,k,v|-> v.err ? s [v.err] :s]) ))
And magically spiff does the work just by calling
spiff merge closure.yaml graph.yaml utilities.yaml
closures :
a :
deps :
- c
- b
- a
err :
- a
- c
- a
b :
deps : []
err : []
c :
deps :
- a
- b
- c
err :
- a
- c
- a
d :
deps :
- b
err : []
e :
deps :
- d
- b
err : []
cycles :
- - a
- c
- a
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
The evaluation of dynaml expressions may fail because of several reasons:
If a dynaml expression cannot be resolved to a value, it is reported by the spiff merge
operation using the following layout:
(( <failed expression> )) in <file> <path to node> (<referred path>) <tag><issue>
(( min_ip("10") )) in source.yml node.a.[0] () *CIDR argument required
Cyclic dependencies are detected by iterative evaluation until the document is unchanged after a step. Nodes involved in a cycle are therefore typically reported just as unresolved node without a specific issue.
The order of the reported unresolved nodes depends on a classification of the problem, denoted by a dedicated tag. The following tags are used (in reporting order):
แท็ก | ความหมาย |
---|---|
* | error in local dynaml expression |
@ | dependent or involved in cyclic dependencies |
- | subsequent error because of refering to a yaml node with an error |
Problems occuring during inline template processing are reported as nested problems. The classification is propagated to the outer node.
If a problem occurs in nested lamba calls the call stack together with the lamba function and is local binding is listed.
(( 2 + .func(2) )) in local/err.yaml value () *evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 2}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 1}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 0}
... resolution of template 'template' failed
(( z )) in local/err.yaml val ()*'z' not found
In case of parsing errors in dynaml expressions, the error location is shown now. If it is a multi line expression the line a character/symbol number in that line is show, otherwise the line numer is omitted.
((
2 ++ .func(2)
)) in local/err.yaml faulty () *parse error near line 2 symbol 2 - line 2 symbol 3: " "
Spiff provides a Go package ( spiffing
) that can be used to include spiff templates in Go programs.
An example program could look like this:
import (
"fmt"
"math"
"os"
"github.com/mandelsoft/spiff/dynaml"
"github.com/mandelsoft/spiff/spiffing"
)
func func_pow ( arguments [] interface {}, binding dynaml. Binding ) ( interface {}, dynaml. EvaluationInfo , bool ) {
info := dynaml . DefaultInfo ()
if len ( arguments ) != 2 {
return info . Error ( "pow takes 2 arguments" )
}
a , b , err := dynaml . NumberOperands ( arguments [ 0 ], arguments [ 1 ])
if err != nil {
return info . Error ( "%s" , err )
}
_ , i := a .( int64 )
if i {
r := math . Pow ( float64 ( a .( int64 )), float64 ( b .( int64 )))
if float64 ( int64 ( r )) == r {
return int64 ( r ), info , true
}
return r , info , true
} else {
return math . Pow ( a .( float64 ), b .( float64 )), info , true
}
}
var state = `
state: {}
`
var stub = `
unused: (( input ))
ages:
alice: (( pow(2,5) ))
bob: (( alice + 1 ))
`
var template = `
state:
<<<: (( &state ))
random: (( rand("[:alnum:]", 10) ))
ages: (( &temporary ))
example:
name: (( input )) # direct reference to additional values
sum: (( sum[ages|0|s,k,v|->s + v] ))
int: (( pow(2,4) ))
float: 2.1
pow: (( pow(1.1e1,2.1) ))
`
func Error ( err error ) {
if err != nil {
fmt . Fprintf ( os . Stderr , "Error: %s n " , err )
os . Exit ( 1 )
}
}
func main () {
values := map [ string ] interface {}{}
values [ "input" ] = "this is an input"
functions := spiffing . NewFunctions ()
functions . RegisterFunction ( "pow" , func_pow )
spiff , err := spiffing . New (). WithFunctions ( functions ). WithValues ( values )
Error ( err )
pstate , err := spiff . Unmarshal ( "state" , [] byte ( state ))
Error ( err )
pstub , err := spiff . Unmarshal ( "stub" , [] byte ( stub ))
Error ( err )
ptempl , err := spiff . Unmarshal ( "template" , [] byte ( template ))
Error ( err )
result , err := spiff . Cascade ( ptempl , []spiffing. Node { pstub }, pstate )
Error ( err )
b , err := spiff . Marshal ( result )
Error ( err )
newstate , err := spiff . Marshal ( spiff . DetermineState ( result ))
Error ( err )
fmt . Printf ( "==== new state === n " )
fmt . Printf ( "%s n " , string ( newstate ))
fmt . Printf ( "==== result === n " )
fmt . Printf ( "%s n " , string ( b ))
}
It supports