ðchgpt,如果您愿意
#python #ai #chatgpt #gitlab

介绍

如果您来这里搜索一个脚本,该脚本通过Gitlab Yaml文件中的给定订单对作业属性进行分类,那么“我们”设法使一个非常令人满意的内容,几乎没有以前在Python本人的经验。您会在本文的末尾找到它。

我已经广泛搜索了一个工具来对gitlab yaml文件中的作业属性进行排序,但找不到任何东西。我直到最近才将一个人推迟了一个。

我第一次使用shell脚本不满意,因为yq,是在YAML,handles emojis poorlystrips blank lines上使用的最佳外壳工具。

我已经为表情符号使用了解决方法,但总体而言,它的灵活性不足以处理边缘案例。

这是我编写的脚本,记录:

#!/bin/zsh
set -e

IRed='\033[0;91m'    # Red
IGreen='\033[0;92m'  # Green
IYellow='\033[0;93m' # Yellow
COff='\033[0m'       # Text Reset

fix_emojis() {
    echo "${IGreen}Fixing bad yq emojis handling on $1...$COff"
    INPUT_FILE=$1
    OUTPUT_FILE=$2
    # using echo to translate emoji chars from unicode to UTF-8
    # + replacing unnecessary double quotes aroung emoji fields
    echo "${IGreen}$(cat $INPUT_FILE | sed 's/"\(.*\\U0.*\)"/\1/g')$COff" >$OUTPUT_FILE
}

GITLAB_YML_FILE_TO_SORT=${1:-.gitlab-ci.yml}
echo "${IGreen}Processing $GITLAB_YML_FILE_TO_SORT...$COff"

rm -rf .glsort 2>/dev/null
mkdir -p .glsort

rm -rf $GITLAB_YML_FILE_TO_SORT.orig

echo "${IGreen}Reorganizing attributes...$COff"
yq '(.* | select(has("extends") or has("script"))) |= pick(["extends", "stage", "image", "dependencies", "cache", "services", "variables", "before_script", "script", "after_script", "environment", "artifacts", "interruptible", "allow_failure", "when", "rules"] + keys | unique )' $GITLAB_YML_FILE_TO_SORT >.glsort/$GITLAB_YML_FILE_TO_SORT.organized

fix_emojis .glsort/$GITLAB_YML_FILE_TO_SORT.organized .glsort/$GITLAB_YML_FILE_TO_SORT.organized-emojis

echo "${IGreen}Making a blankline-free version of $GITLAB_YML_FILE_TO_SORT for later diff...$COff"
yq '.' $GITLAB_YML_FILE_TO_SORT >.glsort/$GITLAB_YML_FILE_TO_SORT.blankline-free
fix_emojis .glsort/$GITLAB_YML_FILE_TO_SORT.blankline-free .glsort/$GITLAB_YML_FILE_TO_SORT.blankline-free-emojis

echo "${IGreen}Preparing final diff file...$COff"
diff --ignore-blank-lines .glsort/$GITLAB_YML_FILE_TO_SORT.blankline-free-emojis .glsort/$GITLAB_YML_FILE_TO_SORT.organized-emojis >.glsort/$GITLAB_YML_FILE_TO_SORT.diff || echo "${IYellow}diff command has (silently ?) failed, please check your pipeline file, you might have to move a line by yourself$COff"

echo "${IGreen}Patching $GITLAB_YML_FILE_TO_SORT...$COff"
patch $GITLAB_YML_FILE_TO_SORT .glsort/$GITLAB_YML_FILE_TO_SORT.diff || true

if [ -f $GITLAB_YML_FILE_TO_SORT.rej ]; then
    echo "${IYellow} Some diff unapplied, please ignore if special characters problem or line already exists: $COff"
    cat $GITLAB_YML_FILE_TO_SORT.rej
    rm -f $GITLAB_YML_FILE_TO_SORT.rej
fi

echo "${IGreen}Deleting intermediate .glsort files...$COff"
rm -rf .glsort

[ -f $GITLAB_YML_FILE_TO_SORT.orig ] && echo "Original file saved as $GITLAB_YML_FILE_TO_SORT.orig"

我决定必须将这些gitlab yaml文件作为文本文件而不是yaml文件处理,以避免弄乱开发人员的演示选择。

随着Chatgpt的兴起,终于是时候尝试每个人都在互联网上谈论的著名人物了。在写作时,使用的语言版本模型为gpt-3.5。

遇到酷孩子

我没有读太多关于chatgpt的信息。几周前,我的第一个测试是对非代码主题的,是“哇,似乎是人类”和“伙计,你怎么能忘记我之前说过的几句话?”。

,但是现在是时候请求一些代码了!

首先尝试询问脚本

用我近似英语的英语,我先天真地问:

我有一个文件,线没有从空格开始,线以2个空格开头(还有更多的非空格字符,以及以至少4个空格开头的行。

以两个空格开头的线,就在以至少4个空间开头的线块之前。

这些线路块应立即移动并按字母顺序重新排序。

你能把我做一个脚本

chatgpt回答:

当然!这是一个python脚本,应该做您要的事情:

with open('input_file.txt', 'r') as f:
    lines = f.readlines()

output_lines = []
current_block = []

for line in lines:
    if line.startswith('  '):
        current_block.append(line)
    else:
        if current_block:
            current_block.sort()
            output_lines += current_block
            current_block = []
        output_lines.append(line)

if current_block:
    current_block.sort()
    output_lines += current_block

with open('output_file.txt', 'w') as f:
    f.writelines(output_lines)

此脚本在输入文件的行中读取,然后通过它们迭代,跟踪以两个空格开头的任何线路。当遇到非指标线时,脚本检查是否存在当前行块,如果是的,则按字母顺序对块进行分类,并将其附加到输出。然后将非指标线附加到输出上,脚本移至下一行。

如果文件末尾仍然存在当前的线路,则将其排序并附加到输出上。最后,将排序的行写入输出文件。

请注意,这假定输入文件存在并命名为input_file.txt,并且应将输出写入名为output_file.txt的文件。您需要用自己的文件的适当名称替换这些文件名。

我一开始就被吹走了。但是在尝试脚本之后,除了对一些行分类之外,它根本没有做任何好事。

向前两步,退后一步:不是很有生产力的迭代

我花了很多时间试图一步一步地解释他:

这是不预期的,所以我将澄清:

未缩进的线不移动。

缩进2个空间的线是一个块的开始,其中包含具有4个或更多空间的0、1或多个凹痕线。

通过块的第一行按字母顺序移动这些块

一如既往,chatgpt道歉并尝试新事物。

我逐渐意识到是在学习我的要求(并说更好的英语ð)的过程中。那个聊天很快就忘记了。

现在我知道我想要什么:加紧满足

现在我真的知道如何说我想要什么。这是重新开始的时刻,从现在开始,我毫不犹豫地做了多次:

让我汇总我告诉的是一个更好的脚本:

让我们解析文件。不是标准输入,而是您必须打开的文件。

如果当前行没有凹痕,请打印。

如果一条线仅以2个空格开头,请将其放在新的线路中。这些行总是以一个单词开头,然后是“:”。

如果一条线以4个或更多空格开头,请将其放在当前块中。

永远不会排序一个块。

当线为空时,通过比较第一个单词来对它们之间的块进行排序,然后打印所有单词,然后删除它们。

分类不是字母顺序的,而是通过单词的下降顺序:“扩展”,“阶段”,“映像”,“ dipertions”,“ dependencies”,“ cache”,“ cache”,“ services”,“ services”,“变量”,“ tre_script”“” ,“ script”,“ after_script”,“环境”,“伪像”,“可中断”,“ allay_failure”,“ n时”,“”,“规则”

不要忘记以4个空格开头的线也以2个空格开头,因此应在只有2个空间的行前检查它们。

您应该打印障碍物,而不是在循环的末端找到空线。

每次找到一个空线时,都必须在它们之间对块进行排序并清空块结构,然后打印空线,然后继续使用文件的其余部分

在空线和非指标线之间分开案例,因为现在您将其放在其他单个中,然后将其打印两倍相同的行

文件末尾总是有一个空线,因此不需要打印残差块。

我忘记了我可以帮助编写脚本

我意识到我希望chatgpt独自制作脚本,但是我可以与chatgpt一起工作,而不是一遍又一遍地解释什么。

这是“我们”生产力的重大飞跃。

我改进了您的脚本,您能解释一下吗?

# python3 glsort.py input_file.txt > output_file.txt

import sys

# Define the order in which job_attributes should be sorted
ATTRIBUTES_ORDER = ["extends", "stage", "image", "dependencies", "cache", "services", "variables",
                    "before_script", "script", "after_script", "environment", "artifacts",
                    "interruptible", "allow_failure", "when", "rules"]

# Read the filename from the command line arguments
filename = sys.argv[1]

# Initialize variables for tracking job_attributes and lines
job_attributes = []
current_attribute = []


def print_sorted_attributes():
    if job_attributes:
        sorted_job_attributes = sorted(job_attributes, key=lambda b: ATTRIBUTES_ORDER.index(b[0].lstrip().split(':')[0])
                                       if b[0].lstrip().split(':')[0] in ATTRIBUTES_ORDER
                                       else len(ATTRIBUTES_ORDER))
        for block in sorted_job_attributes:
            for line in block:
                print(line, end='')


# Open the file and start reading lines
with open(filename, 'r') as f:
    for line in f:

        # Check if the line starts with 4 or more spaces (i.e., is indented)
        if line.startswith(' ' * 4):
            # Add the line to the current block
            current_attribute.append(line)
        # 'stages' special case
        elif line.lstrip().startswith('-'):
            print(line, end='')
        # Check if the line starts with 2 spaces (i.e., is the start of a new block)
        elif line.startswith(' ' * 2):
            # Add the current block to the list of job_attributes (if it's not empty)
            current_attribute = [line]
            job_attributes.append(current_attribute)
        # Otherwise, the line is not indented and should be printed
        else:
            print_sorted_attributes()

            # Reset the job_attributes variable and continue to the next line
            job_attributes = []
            current_attribute = []
            print(line, end='')

# If there are any remaining job_attributes at the end of the file, sort and print them
print_sorted_attributes()

chatgpt响应:

当然,这是对更新脚本的解释:

  • 我们首先定义一个列表属性_erder,该属性指定了应在其中分类作业属性的顺序。此列表包括按所需顺序出现在输入文件中的所有可能属性。

[...]

chatgpt可以用特定代码解释自己

一些晦涩的代码使我无法进一步重构。再一次,我没有充分的理由将自己拒之门外,Chatgpt可以解释自己:

当您给出这个时,您可以解释吗?

sorted_blocks = sorted(blocks, key=lambda b:
    SORT_ORDER.index(b[0].split(':')[0])
    if b[0].split(':')[0] in SORT_ORDER else len(SORT_ORDER))

当然!

使用自定义键函数进行分类的代码行定义为块列表的排序版本。

使用lambda函数定义了密钥函数,该函数将块b作为输入,并在sort_order列表中返回其第一个单词的位置,或者如果第一个单词不在sort_order中,则返回sort_order的长度。

这是关键功能的分解:

b[0] selects the first line of the block, which contains the keyword and colon character.
b[0].split(':')[0] splits this line on the colon character and selects the first element, which is the keyword.
if b[0].split(':')[0] in SORT_ORDER checks if the keyword is in the SORT_ORDER list.
SORT_ORDER.index(b[0].split(':')[0]) returns the position of the keyword in SORT_ORDER if it is in the list.
else len(SORT_ORDER) returns the length of SORT_ORDER if the keyword is not in the list.

将所有内容汇总在一起,如果键函数在列表中,则返回第一个单词的位置,或者列表的长度(如果不是)。这样可以确保在具有未识别的关键字的块之前对具有识别关键字的块进行排序,并且在每个组中,根据其在sort_order列表中的第一个单词的位置对块进行排序。

sorted()函数然后将此键函数应用于块中的每个块以确定排序顺序,并将排序列表返回为sorted_blocks。

chatgpt可以添加日志以帮助了解算法

我对python的低了解可以帮助您在晦涩的部分中进行一些日志。 chatgpt没问题:

我们可以在lambda过程中添加印刷品以更好地了解运行时发生的事情吗?

响应:

当然!这是相同的lambda功能,并带有添加的打印语句:

sorted_blocks = sorted(blocks, key=lambda b: (b[0], print(b[0]), SORT_ORDER.index(b[0].split(':')[0]) if b[0].split(':')[0] in SORT_ORDER else len(SORT_ORDER), print(SORT_ORDER.index(b[0].split(':')[0]) if b[0].split(':')[0] in SORT_ORDER else len(SORT_ORDER))))

这将打印出B [0]的值,并通过使用sort_order列表对其进行排序。它应该让您更好地了解运行时发生的事情。

chatgpt可以生成脚本标题...超出预期

我只是问:

您可以生成我可以在脚本中粘贴的标题解释吗?

我得到了回应:

GLSORT

Script for sorting GitLab CI job attributes by a predefined order.

Usage:
  python3 glsort.py input_file.txt > output_file.txt

The script reads a GitLab CI YAML file and sorts the job attributes by a predefined order,
which can be customized by modifying the ATTRIBUTES_ORDER list. The sorted YAML file is
output to stdout and can be redirected to a file.

Algorithm:
- Initialize variables for tracking job attributes and lines
- Define the order in which job attributes should be sorted
- Read the filename from the command line arguments
- Open the file and start reading lines
- If the line starts with 4 or more spaces, add it to the current job attribute
- If the line starts with 2 spaces, start a new job attribute
- If the line is not indented, print the sorted job attributes and the line
- When an empty line is found, sort the job attributes and print them, then print the empty line
- If there are any remaining job attributes at the end of the file, sort and print them
- Job attributes are sorted by the order defined in ATTRIBUTES_ORDER
- 'stages' section is a special case and will be printed as is

当我阅读这个令人满意的文本(非常好)时,我吓坏了。让我强调这部分:
The script reads a GitLab CI YAML file and sorts the job attributes by a predefined order

gitlab!?我没有一次告诉chatgpt我们正在处理gitlab yaml文件...

我不得不问:

您是如何理解这是gitlab ci的工作属性属性的?

chatgpt响应:

从输入文件的结构和任务描述中提到的特定细节中,很明显该脚本旨在对gitlab ci作业属性进行排序。 GitLab CI使用YAML文件来定义作业及其属性,这些文件遵循特定的结构。任务描述提到了关键细节,例如代码块,以单词开头,然后是结肠(例如“脚本:”),使用凹痕来表示块层次结构以及存在特定的作业属性,例如“ image”, “脚本”和“变量”。这些详细信息与gitlab ci yaml文件中使用的结构和语法保持一致。

非常整洁,哼? ð

有关小改进

为了避免过多地忘记和返工的倾向,您可以帮助专注于代码的一部分或部分要求。然后将更改自己添加到代码中。

它也有助于避免达到免费版本的最大文本大小。

一个示例:

让我们专注于脚本的一小部分。

请给我一个Python脚本:

  • 如果是文件,则打印第一个参数
  • 打印“文件夹”如果是文件夹
  • 打印“ .gitlab-ci.yml”如果没有参数
  • 在所有其他情况下打印用法

答案:

这是一个python脚本,该脚本检查第一个命令行参数并打印适当的输出:

import os
import sys

if len(sys.argv) == 1:
    print(".gitlab-ci.yml")
    sys.exit(0)

arg = sys.argv[1]

if os.path.isfile(arg):
    print(arg)
elif os.path.isdir(arg):
    print("folder")
else:
    print("Usage: python script.py [filename or foldername]")
    sys.exit(1)

另一个示例:

是否可以为此功能定义忽略的文件夹列表?

def process_files_recursively(directory):
    for root, dirs, files in os.walk(directory):
        for file in files:
            if GITLAB_FILENAME_REGEX.match(file):
                file_path = os.path.join(root, file)
                process_file(file_path)

响应:

是的,您可以定义忽略目录的列表,并使用它来修改OS.Walk函数中的DIRS列表。这是执行此功能的功能的更新版本:

def process_files_recursively(directory):
    ignored_dirs = [".git", ".svn", "node_modules"] # List of directories to ignore
    for root, dirs, files in os.walk(directory):
        # Remove ignored directories from the list
        dirs[:] = [d for d in dirs if d not in ignored_dirs]
        for file in files:
            if GITLAB_FILENAME_REGEX.match(file):
                file_path = os.path.join(root, file)
                process_file(file_path)

结论

总的来说,在我看来,Chatgpt 3.5似乎是一个天才9 years old child,在我的话语之外理解的问题。它想取悦我,而不是发现真相。但这是一个很棒的推动者,通过令人满意的引导代码提高了我的生产率。

在大约2个小时内,“我们”用我不熟练的语言制作了一个新脚本。

在这里,任何人都可以使用:

#!/usr/local/bin/python

# GitLab Yaml sorter

# Script for sorting GitLab CI job attributes by a predefined order.

# Usage : gitlab-yaml-sort.py [file|folder]

# Parameters :
# file : the file to to be processed in-place
# folder : a folder where .gitlab-ci.yml (and similarly named files) will be processed recursively
# no parameter : .gitlab-ci.yml in current folder will be processed

# The script edits in-place a GitLab CI YAML file and sorts the job attributes by a predefined order.
# Unknown attributes are pushed to the end.
# If any error occurs while processing, the original file is kept has a .bak file.

import sys
import os
import re
import shutil

# Define the order in which job_attributes should be sorted
ATTRIBUTES_ORDER = ["extends", "stage", "image", "needs", "dependencies", "cache", "services", "variables",
                    "before_script", "script", "after_script", "coverage", "environment", "artifacts",
                    "interruptible", "allow_failure", "when", "rules", "tags"]

# Define special keywords to skip sorting of the following block. 'default' is a special keyword but should be sorted
IGNORED_TOP_LEVEL_KEYWORDS = ["stages", "includes", "variables"]

# List of directories to ignore
IGNORED_DIRECTORIES = [".git", ".history",
                       "node_modules", "tmp", ".gitlab-ci-local", "build-docs"]

# Define regex for matching filenames
GITLAB_FILENAME_REGEX = re.compile(r'.*\.gitlab-ci.*\.ya?ml$')


def sort_job_attributes(job_attributes):
    sorted_job_attributes = sorted(job_attributes, key=lambda b: ATTRIBUTES_ORDER.index(b[0].lstrip().split(':')[0])
                                   if b[0].lstrip().split(':')[0] in ATTRIBUTES_ORDER
                                   else len(ATTRIBUTES_ORDER))
    return [line for block in sorted_job_attributes for line in block]


def process_file(filename):

    # make a backup of the original file in case of error processing it
    shutil.copyfile(filename, filename + ".bak")

    # Initialize variables for tracking job_attributes and lines
    job_attributes = []
    current_attribute = []
    is_current_block_sortable = True
    last_line_was_empty = False

    with open(filename, 'r') as f:
        lines = f.readlines()

    with open(filename, 'w') as f:
        for line in lines:
            # Check if the line starts with special keywords
            if any(line.startswith(keyword) for keyword in IGNORED_TOP_LEVEL_KEYWORDS):
                # flush current attribute content
                f.write(''.join(sort_job_attributes(job_attributes)))
                job_attributes = []
                current_attribute = []
                if last_line_was_empty:
                    f.write('\n')
                # don't reorganize and write
                f.write(line)
                is_current_block_sortable = False
            # Check if the line is indented in a non-sortable block
            elif line.startswith(' ' * 2) and not is_current_block_sortable:
                # Just write
                f.write(line)
            # Check if the line is indented to sub-sublevel
            elif line.startswith(' ' * 4):
                # Add the line to the current block
                current_attribute.append(line)
            # Check if the line is indented to sublevel : this is the beginning of a new block to be sorted
            elif line.startswith(' ' * 2):
                # Add the current block to the list of job_attributes (if it's not empty)
                current_attribute = [line]
                job_attributes.append(current_attribute)
                is_current_block_sortable = True
            # Handle special case when there are empty lines in attributes (such as 'script')
            elif line.strip() == '':
                last_line_was_empty = True
            # Otherwise, the line is not indented and should be written
            else:
                f.write(''.join(sort_job_attributes(job_attributes)))
                if last_line_was_empty:
                    f.write('\n')
                f.write(line)
                # Reset variables and continue to the next line
                job_attributes = []
                current_attribute = []
                is_current_block_sortable = True
                last_line_was_empty = False

        if current_attribute:
            f.write(''.join(sort_job_attributes(job_attributes)))

    print("successfully sorted job attributes in " + filename)
    os.remove(filename + ".bak")


def process_files_recursively(directory):
    for root, dirs, files in os.walk(directory):
        # Remove ignored directories from the list
        dirs[:] = [d for d in dirs if d not in IGNORED_DIRECTORIES]
        for file in files:
            if GITLAB_FILENAME_REGEX.match(file):
                file_path = os.path.join(root, file)
                process_file(file_path)


if __name__ == '__main__':
    if len(sys.argv) == 1:
        process_file(".gitlab-ci.yml")
    elif len(sys.argv) == 2:
        path = sys.argv[1]
        if os.path.isfile(path):
            process_file(path)
        elif os.path.isdir(path):
            process_files_recursively(path)
    else:
        print("usage: python {} [file|folder]".format(sys.argv[0]))