diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1de0688 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,1393 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[*.bazelproject] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_projectview_keep_indents_on_empty_lines = false + +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_block_comment_add_space = false +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.dcl] +ij_declarative_keep_indents_on_empty_lines = false + +[*.gsp] +ij_gsp_keep_indents_on_empty_lines = false + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = true +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = true +ij_java_annotation_new_line_in_record_component = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_field_with_annotations = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_blank_lines_between_record_components = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_delete_unused_module_imports = false +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_entity_dd_prefix = +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_prefix = +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_prefix = +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_li_suffix = +ij_java_entity_pk_class = java.lang.String +ij_java_entity_ri_prefix = +ij_java_entity_ri_suffix = +ij_java_entity_vo_prefix = +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_filter_class_prefix = +ij_java_filter_class_suffix = +ij_java_filter_dd_prefix = +ij_java_filter_dd_suffix = +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_generate_use_type_annotation_before_type = true +ij_java_if_brace_force = never +ij_java_imports_layout = java.**, |, javax.**, |, groovy.**, org.apache.groovy.**, org.codehaus.groovy.**, |, jakarta.**, |, *, |, io.spring.**, org.springframework.**, |, grails.**, org.apache.grails.**, org.grails.**, |, $* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_on_demand_import_from_same_package_first = true +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_listener_class_prefix = +ij_java_listener_class_suffix = +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_message_dd_prefix = +ij_java_message_dd_suffix = EJB +ij_java_message_eb_prefix = +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 999 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_preserve_module_imports = true +ij_java_record_components_wrap = normal +ij_java_repeat_annotations = +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +ij_java_servlet_class_prefix = +ij_java_servlet_class_suffix = +ij_java_servlet_dd_prefix = +ij_java_servlet_dd_suffix = +ij_java_session_dd_prefix = +ij_java_session_dd_suffix = EJB +ij_java_session_eb_prefix = +ij_java_session_eb_suffix = Bean +ij_java_session_hi_prefix = +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_li_suffix = +ij_java_session_ri_prefix = +ij_java_session_ri_suffix = +ij_java_session_si_prefix = +ij_java_session_si_suffix = Service +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_inside_block_braces_when_body_is_present = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false +ij_java_wrap_semicolon_after_call_chain = false + +[*.less] +indent_size = 2 +ij_less_align_closing_brace_with_properties = false +ij_less_blank_lines_around_nested_selector = 1 +ij_less_blank_lines_between_blocks = 1 +ij_less_block_comment_add_space = false +ij_less_brace_placement = 0 +ij_less_enforce_quotes_on_format = false +ij_less_hex_color_long_format = false +ij_less_hex_color_lower_case = false +ij_less_hex_color_short_format = false +ij_less_hex_color_upper_case = false +ij_less_keep_blank_lines_in_code = 2 +ij_less_keep_indents_on_empty_lines = false +ij_less_keep_single_line_blocks = false +ij_less_line_comment_add_space = false +ij_less_line_comment_at_first_column = false +ij_less_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow +ij_less_space_after_colon = true +ij_less_space_before_opening_brace = true +ij_less_use_double_quotes = true +ij_less_value_alignment = 0 + +[*.proto] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_protobuf_keep_blank_lines_in_code = 2 +ij_protobuf_keep_indents_on_empty_lines = false +ij_protobuf_keep_line_breaks = true +ij_protobuf_space_after_comma = true +ij_protobuf_space_before_comma = false +ij_protobuf_spaces_around_assignment_operators = true +ij_protobuf_spaces_within_braces = false +ij_protobuf_spaces_within_brackets = false + +[*.sass] +indent_size = 2 +ij_sass_align_closing_brace_with_properties = false +ij_sass_blank_lines_around_nested_selector = 1 +ij_sass_blank_lines_between_blocks = 1 +ij_sass_brace_placement = 0 +ij_sass_enforce_quotes_on_format = false +ij_sass_hex_color_long_format = false +ij_sass_hex_color_lower_case = false +ij_sass_hex_color_short_format = false +ij_sass_hex_color_upper_case = false +ij_sass_keep_blank_lines_in_code = 2 +ij_sass_keep_indents_on_empty_lines = false +ij_sass_keep_single_line_blocks = false +ij_sass_line_comment_add_space = false +ij_sass_line_comment_at_first_column = false +ij_sass_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow +ij_sass_space_after_colon = true +ij_sass_space_before_opening_brace = true +ij_sass_use_double_quotes = true +ij_sass_value_alignment = 0 + +[*.scss] +indent_size = 2 +ij_scss_align_closing_brace_with_properties = false +ij_scss_blank_lines_around_nested_selector = 1 +ij_scss_blank_lines_between_blocks = 1 +ij_scss_block_comment_add_space = false +ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false +ij_scss_hex_color_long_format = false +ij_scss_hex_color_lower_case = false +ij_scss_hex_color_short_format = false +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_line_comment_add_space = false +ij_scss_line_comment_at_first_column = false +ij_scss_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow +ij_scss_space_after_colon = true +ij_scss_space_before_opening_brace = true +ij_scss_use_double_quotes = true +ij_scss_value_alignment = 0 + +[*.vue] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_vue_indent_children_of_top_level = template +ij_vue_interpolation_new_line_after_start_delimiter = true +ij_vue_interpolation_new_line_before_end_delimiter = true +ij_vue_interpolation_wrap = off +ij_vue_keep_indents_on_empty_lines = false +ij_vue_spaces_within_interpolation_expressions = true + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ad,*.adoc,*.asciidoc,.asciidoctorconfig}] +ij_asciidoc_blank_lines_after_header = 1 +ij_asciidoc_blank_lines_keep_after_header = 1 +ij_asciidoc_formatting_enabled = true +ij_asciidoc_one_sentence_per_line = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.ats,*.cts,*.mts,*.ts}] +ij_continuation_indent_size = 4 +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = true +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = off +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = off +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_class_decorator_wrap = split_into_lines +ij_typescript_class_field_decorator_wrap = off +ij_typescript_class_method_decorator_wrap = off +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = never +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = keep +ij_typescript_enum_constants_wrap = on_every_item +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = never +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_function_parameter_decorator_wrap = off +ij_typescript_if_brace_force = never +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = on_every_item +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = true +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 2 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_methods_in_one_line = false +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = off +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = off +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_object_types_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_property_prefix = +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = false +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = false +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = off +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = true +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_import_type = auto +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = true +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = never +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.cjs,*.es6,*.js,*.mjs}] +ij_continuation_indent_size = 4 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = true +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = off +ij_javascript_assignment_wrap = off +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = off +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_class_decorator_wrap = split_into_lines +ij_javascript_class_field_decorator_wrap = off +ij_javascript_class_method_decorator_wrap = off +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = never +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = keep +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = never +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = false +ij_javascript_force_semicolon_style = false +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_function_parameter_decorator_wrap = off +ij_javascript_if_brace_force = never +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 2 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = false +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = off +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_object_types_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_property_prefix = +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_import_type = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = true +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = never +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.cjsx,*.coffee}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_coffeescript_align_function_body = false +ij_coffeescript_align_imports = false +ij_coffeescript_align_multiline_array_initializer_expression = true +ij_coffeescript_align_multiline_parameters = true +ij_coffeescript_align_multiline_parameters_in_calls = false +ij_coffeescript_align_object_properties = 0 +ij_coffeescript_align_union_types = false +ij_coffeescript_align_var_statements = 0 +ij_coffeescript_array_initializer_new_line_after_left_brace = false +ij_coffeescript_array_initializer_right_brace_on_new_line = false +ij_coffeescript_array_initializer_wrap = normal +ij_coffeescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/** +ij_coffeescript_blank_lines_around_function = 1 +ij_coffeescript_call_parameters_new_line_after_left_paren = false +ij_coffeescript_call_parameters_right_paren_on_new_line = false +ij_coffeescript_call_parameters_wrap = normal +ij_coffeescript_chained_call_dot_on_new_line = true +ij_coffeescript_class_decorator_wrap = split_into_lines +ij_coffeescript_class_field_decorator_wrap = off +ij_coffeescript_class_method_decorator_wrap = off +ij_coffeescript_comma_on_new_line = false +ij_coffeescript_enforce_trailing_comma = keep +ij_coffeescript_field_prefix = _ +ij_coffeescript_file_name_style = relaxed +ij_coffeescript_force_quote_style = false +ij_coffeescript_force_semicolon_style = false +ij_coffeescript_function_expression_brace_style = end_of_line +ij_coffeescript_function_parameter_decorator_wrap = off +ij_coffeescript_import_merge_members = global +ij_coffeescript_import_prefer_absolute_path = global +ij_coffeescript_import_sort_members = true +ij_coffeescript_import_sort_module_name = false +ij_coffeescript_import_use_node_resolution = true +ij_coffeescript_imports_wrap = on_every_item +ij_coffeescript_indent_chained_calls = true +ij_coffeescript_indent_package_children = 0 +ij_coffeescript_jsx_attribute_value = braces +ij_coffeescript_keep_blank_lines_in_code = 2 +ij_coffeescript_keep_first_column_comment = true +ij_coffeescript_keep_indents_on_empty_lines = false +ij_coffeescript_keep_line_breaks = true +ij_coffeescript_keep_simple_methods_in_one_line = false +ij_coffeescript_method_parameters_new_line_after_left_paren = false +ij_coffeescript_method_parameters_right_paren_on_new_line = false +ij_coffeescript_method_parameters_wrap = off +ij_coffeescript_object_literal_wrap = on_every_item +ij_coffeescript_object_types_wrap = on_every_item +ij_coffeescript_prefer_as_type_cast = false +ij_coffeescript_prefer_explicit_types_function_expression_returns = false +ij_coffeescript_prefer_explicit_types_function_returns = false +ij_coffeescript_prefer_explicit_types_vars_fields = false +ij_coffeescript_property_prefix = +ij_coffeescript_reformat_c_style_comments = false +ij_coffeescript_space_after_comma = true +ij_coffeescript_space_after_dots_in_rest_parameter = false +ij_coffeescript_space_after_generator_mult = true +ij_coffeescript_space_after_property_colon = true +ij_coffeescript_space_after_type_colon = true +ij_coffeescript_space_after_unary_not = false +ij_coffeescript_space_before_async_arrow_lparen = true +ij_coffeescript_space_before_class_lbrace = true +ij_coffeescript_space_before_comma = false +ij_coffeescript_space_before_function_left_parenth = true +ij_coffeescript_space_before_generator_mult = false +ij_coffeescript_space_before_property_colon = false +ij_coffeescript_space_before_type_colon = false +ij_coffeescript_space_before_unary_not = false +ij_coffeescript_spaces_around_additive_operators = true +ij_coffeescript_spaces_around_arrow_function_operator = true +ij_coffeescript_spaces_around_assignment_operators = true +ij_coffeescript_spaces_around_bitwise_operators = true +ij_coffeescript_spaces_around_equality_operators = true +ij_coffeescript_spaces_around_logical_operators = true +ij_coffeescript_spaces_around_multiplicative_operators = true +ij_coffeescript_spaces_around_relational_operators = true +ij_coffeescript_spaces_around_shift_operators = true +ij_coffeescript_spaces_around_unary_operator = false +ij_coffeescript_spaces_within_array_initializer_braces = false +ij_coffeescript_spaces_within_array_initializer_brackets = false +ij_coffeescript_spaces_within_imports = false +ij_coffeescript_spaces_within_index_brackets = false +ij_coffeescript_spaces_within_interpolation_expressions = false +ij_coffeescript_spaces_within_method_call_parentheses = false +ij_coffeescript_spaces_within_method_parentheses = false +ij_coffeescript_spaces_within_object_braces = false +ij_coffeescript_spaces_within_object_literal_braces = false +ij_coffeescript_spaces_within_object_type_braces = true +ij_coffeescript_spaces_within_range_brackets = false +ij_coffeescript_spaces_within_type_assertion = false +ij_coffeescript_spaces_within_union_types = true +ij_coffeescript_union_types_wrap = on_every_item +ij_coffeescript_use_chained_calls_group_indents = false +ij_coffeescript_use_double_quotes = true +ij_coffeescript_use_explicit_js_extension = auto +ij_coffeescript_use_import_type = auto +ij_coffeescript_use_path_mapping = always +ij_coffeescript_use_public_modifier = false +ij_coffeescript_use_semicolon_after_statement = false +ij_coffeescript_var_declaration_wrap = normal + +[{*.ft,*.vm,*.vsl}] +ij_vtl_keep_indents_on_empty_lines = false + +[{*.gant,*.groovy,*.gson,*.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_add_space = false +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 999 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enable_groovydoc_formatting = true +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_ginq_general_clause_wrap_policy = 2 +ij_groovy_ginq_having_wrap_policy = 1 +ij_groovy_ginq_indent_having_clause = true +ij_groovy_ginq_indent_on_clause = true +ij_groovy_ginq_on_wrap_policy = 1 +ij_groovy_ginq_space_after_keyword = true +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = java.**, |, javax.**, |, groovy.**, org.apache.groovy.**, org.codehaus.groovy.**, |, jakarta.**, |, *, |, io.spring.**, org.springframework.**, |, grails.**, org.apache.grails.**, org.grails.**, |, $* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_add_space_on_reformat = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 999 +ij_groovy_packages_to_use_import_on_demand = +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_record_parentheses = false +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_chain_calls_after_dot = false +ij_groovy_wrap_long_lines = false + +[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}] +indent_size = 2 +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.hcl,*.nomad}] +indent_size = 2 +ij_hcl_keep_blank_lines_in_code = 2 +ij_hcl_keep_indents_on_empty_lines = false +ij_hcl_keep_line_breaks = true +ij_hcl_space_after_comma = true +ij_hcl_space_before_comma = false +ij_hcl_spaces_around_assignment_operators = true +ij_hcl_spaces_within_braces = false +ij_hcl_spaces_within_brackets = false +ij_hcl_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p +ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span, pre, textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.http,*.rest}] +ij_continuation_indent_size = 4 +ij_http-request_call_parameters_wrap = normal +ij_http-request_method_parameters_wrap = split_into_lines +ij_http-request_space_before_comma = true +ij_http-request_spaces_around_assignment_operators = true + +[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] +ij_jsp_jsp_prefer_comma_separated_import_list = false +ij_jsp_keep_indents_on_empty_lines = false + +[{*.jspx,*.tagx}] +ij_jspx_keep_indents_on_empty_lines = false + +[{*.kt,*.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_collection_literal_expression = false +ij_kotlin_allow_trailing_comma_context_receiver_list = true +ij_kotlin_allow_trailing_comma_destructuring_declaration = true +ij_kotlin_allow_trailing_comma_function_literal = true +ij_kotlin_allow_trailing_comma_indices = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_allow_trailing_comma_type_argument_list = false +ij_kotlin_allow_trailing_comma_type_parameter_list = true +ij_kotlin_allow_trailing_comma_value_argument_list = false +ij_kotlin_allow_trailing_comma_value_parameter_list = true +ij_kotlin_allow_trailing_comma_when_entry = true +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_function_context_parameters_wrap = split_into_lines +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^ +ij_kotlin_indent_before_arrow_on_new_line = true +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = true +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**, io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_property_context_parameters_wrap = split_into_lines +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_elvis = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.pb,*.textproto,*.txtpb}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_prototext_keep_blank_lines_in_code = 2 +ij_prototext_keep_indents_on_empty_lines = false +ij_prototext_keep_line_breaks = true +ij_prototext_space_after_colon = true +ij_prototext_space_after_comma = true +ij_prototext_space_before_colon = false +ij_prototext_space_before_comma = false +ij_prototext_spaces_within_braces = true +ij_prototext_spaces_within_brackets = false + +[{*.properties,spring.handlers,spring.schemas}] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] +ij_qute_keep_indents_on_empty_lines = false + +[{*.tf,*.tfvars}] +indent_size = 2 +ij_hcl-terraform_array_wrapping = normal +ij_hcl-terraform_import_providers_automatically = true +ij_hcl-terraform_keep_blank_lines_in_code = 2 +ij_hcl-terraform_keep_indents_on_empty_lines = false +ij_hcl-terraform_keep_line_breaks = true +ij_hcl-terraform_line_commenter_character = pound_sign_( #) +ij_hcl-terraform_object_wrapping = normal +ij_hcl-terraform_property_alignment = on_equals +ij_hcl-terraform_run_tf_fmt_on_reformat = true +ij_hcl-terraform_space_after_comma = true +ij_hcl-terraform_space_before_comma = false +ij_hcl-terraform_spaces_around_assignment_operators = true +ij_hcl-terraform_spaces_within_braces = false +ij_hcl-terraform_spaces_within_brackets = false +ij_hcl-terraform_wrap_long_lines = false + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,pdm.lock,poetry.lock,uv.lock}] +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_line_comment_add_space = false +ij_yaml_line_comment_add_space_on_reformat = false +ij_yaml_line_comment_at_first_column = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 3840251..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle - -name: Build - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - workflow_dispatch: - - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - java-version: '11' - distribution: 'adopt' - cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build with Gradle - run: ./gradlew build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e689a8f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,110 @@ +name: "CI" +on: + push: + branches: + - '[0-9]+.[0-9]+.x' + - 'main' + pull_request: + workflow_dispatch: +env: + JAVA_DISTRIBUTION: 'liberica' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +jobs: + build: + name: "Build & Test" + runs-on: ubuntu-24.04 + steps: + - name: "Output Agent IP" # in the event your agent has network issues, you can use this to debug + run: curl -s https://api.ipify.org + - name: "📥 Checkout repository" + uses: actions/checkout@v6 + - name: "Export .sdkmanrc properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + with: + file: ".sdkmanrc" + prefix: "SDKMANRC_" + - name: "Determine Java Version" + run: echo "SDKMANRC_java=${{ env.SDKMANRC_java }}" | sed 's/-.*//' >> $GITHUB_ENV + - name: "☕️ Setup JDK" + uses: actions/setup-java@v5 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.SDKMANRC_java }} + - name: 'Ensure Common Build Date' # to ensure a reproducible build + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" + - name: "Export gradle.properties properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + with: + build-scan-publish: ${{ env.ciBuildScanPublish }} + build-scan-terms-of-use-url: ${{ env.ciBuildScanTermsOfUseUrl }} + build-scan-terms-of-use-agree: ${{ env.ciBuildScanTermsOfUseAgree }} + - name: "🔨 Build project without tests" + if: ${{ contains(github.event.head_commit.message, '[skip tests]') }} + run: > + ./gradlew build + --continue + --stacktrace + -PskipTests + -PskipCodeStyle + - name: "🔨 Build project with tests" + if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }} + run: > + ./gradlew build + --continue + --stacktrace + --rerun-tasks + -PskipCodeStyle + publish: + # only run the publishing task on this repo (not on forks) + if: github.repository_owner == 'gpc' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + needs: build + name: "Publish Snapshot" + runs-on: ubuntu-24.04 + steps: + - name: "Output Agent IP" # in the event your agent has network issues, you can use this to debug + run: curl -s https://api.ipify.org + - name: "📥 Checkout repository" + uses: actions/checkout@v6 + - name: "Export .sdkmanrc properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + with: + file: ".sdkmanrc" + prefix: "SDKMANRC_" + - name: "Determine Java Version" + run: echo "SDKMANRC_java=${{ env.SDKMANRC_java }}" | sed 's/-.*//' >> $GITHUB_ENV + - name: "☕️ Setup JDK" + uses: actions/setup-java@v5 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.SDKMANRC_java }} + - name: 'Ensure Common Build Date' # to ensure a reproducible build + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" + - name: "Export gradle.properties properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + with: + build-scan-publish: ${{ env.ciBuildScanPublish }} + build-scan-terms-of-use-url: ${{ env.ciBuildScanTermsOfUseUrl }} + build-scan-terms-of-use-agree: ${{ env.ciBuildScanTermsOfUseAgree }} + - name: "📤 Publish Snapshot Artifacts" + env: + GRAILS_PUBLISH_RELEASE: 'false' + MAVEN_PUBLISH_URL: 'https://central.sonatype.com/repository/maven-snapshots/' + MAVEN_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + MAVEN_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + run: > + ./gradlew publish + --no-build-cache + --rerun-tasks + - name: "📜 Generate Documentation" + run: ./gradlew docs + - name: "🚀 Publish to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GRADLE_PUBLISH_RELEASE: 'false' + SOURCE_FOLDER: build/docs diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 0000000..4951a17 --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -0,0 +1,26 @@ +name: "Release - Drafter" +on: + issues: + types: [ closed,reopened ] + push: + branches: + - '[0-9]+.[0-9]+.x' + - 'main' + pull_request: + types: [ opened, reopened, synchronize, labeled ] + workflow_dispatch: +# queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits +concurrency: + group: release-pipeline + cancel-in-progress: false +jobs: + update_release_draft: + permissions: + contents: write # write permission is required to create a github release + pull-requests: write # write permission is required for auto-labeler + runs-on: ubuntu-24.04 + steps: + - name: "📝 Update Release Draft" + uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9051e0e..ed65e82 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,90 +1,197 @@ -name: Release +name: "Release" on: - release: - types: [ published ] + release: + types: [ published ] +permissions: { } +env: + # To prevent throttling of the GitHub api, + # include the GitHub token in an environment variable + # since the build will check for it + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GRAILS_PUBLISH_RELEASE: 'true' + JAVA_DISTRIBUTION: 'liberica' + REPO_NAME: ${{ github.event.repository.name }} + RUN_ID: ${{ github.run_id }} + TAG: ${{ github.event.release.tag_name }} + VERSION: 'will be computed in each job' +concurrency: + group: release-pipeline + cancel-in-progress: false jobs: - release: - runs-on: ubuntu-latest + publish: + name: "Stage Jar Files" + permissions: + packages: read # pre-release workflow + contents: write # to create a release + issues: write # to modify milestones + runs-on: ubuntu-24.04 + steps: + - name: "📝 Establish release version" + run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV" + - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it + run: curl -s https://api.ipify.org + - name: "📥 Checkout repository" + uses: actions/checkout@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ env.TAG }} + - name: 'Ensure Common Build Date' # to ensure a reproducible build + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" + - name: "Ensure source files use common date" + run: | + find . -depth \( -type f -o -type d \) -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + + - name: "🔐 Generate key file for artifact signing" env: - GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }} - GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - token: ${{ secrets.GH_TOKEN }} - - uses: gradle/wrapper-validation-action@v1 - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: 8 - - name: Get latest release version number - id: get_version - uses: battila7/get-version-action@v2 - - name: Run pre-release - uses: micronaut-projects/github-actions/pre-release@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - - name: Publish to Sonatype OSSRH - env: - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} - SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} - SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} - SECRING_FILE: ${{ secrets.SECRING_FILE }} - RELEASE_VERSION: ${{ steps.get_version.outputs.version-without-v }} - run: | - echo "${SECRING_FILE}" | base64 -d > "${GITHUB_WORKSPACE}/secring.gpg" - echo "Publishing Artifacts for $RELEASE_VERSION" - (set -x; ./gradlew -Pversion="${RELEASE_VERSION}" -Psigning.secretKeyRingFile="${GITHUB_WORKSPACE}/secring.gpg" publishToSonatype closeAndReleaseSonatypeStagingRepository --no-daemon) - - name: Bump patch version by one - uses: actions-ecosystem/action-bump-semver@v1 - id: bump_semver - with: - current_version: ${{steps.get_version.outputs.version-without-v }} - level: patch - - name: Set version in gradle.properties - env: - NEXT_VERSION: ${{ steps.bump_semver.outputs.new_version }} - run: | - echo "Preparing next snapshot" - ./gradlew snapshotVersion -Pversion="${NEXT_VERSION}" - - name: Commit & Push changes - uses: actions-js/push@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - author_name: ${{ secrets.GIT_USER_NAME }} - author_email: $${ secrets.GIT_USER_EMAIL }} - message: 'Set version to next SNAPSHOT' - - name: Check file documentation exists - id: check_documentation - uses: andstor/file-existence-action@v1 - with: - files: "src/docs" - - name: Build documentation - if: steps.check_documentation.outputs.files_exists == 'true' - env: - RELEASE_VERSION: ${{ steps.get_version.outputs.version-without-v }} - run: | - ./gradlew asciidoctor -Pversion="${RELEASE_VERSION}" - - name: Export Gradle Properties - uses: micronaut-projects/github-actions/export-gradle-properties@master - - name: Publish to Github Pages - if: steps.check_documentation.outputs.files_exists == 'true' && success() - uses: micronaut-projects/github-pages-deploy-action@master - env: - BETA: ${{ steps.get_version.outputs.isPrerelase }} - TARGET_REPOSITORY: ${{ github.repository }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - BRANCH: gh-pages - FOLDER: build/asciidoc - DOC_FOLDER: latest - COMMIT_EMAIL: ${{ secrets.GIT_USER_EMAIL }} - COMMIT_NAME: ${{ secrets.GIT_USER_NAME }} - VERSION: ${{ steps.get_version.outputs.version-without-v }} - - name: Run post-release - if: success() - uses: micronaut-projects/github-actions/post-release@master - with: - token: ${{ secrets.GITHUB_TOKEN }} + SECRING_FILE: ${{ secrets.SECRING_FILE }} + run: echo "$SECRING_FILE" | base64 -d > ${{ github.workspace }}/secring.gpg + - name: "Export .sdkmanrc properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + with: + file: ".sdkmanrc" + prefix: "SDKMANRC_" + - name: "Determine Java Version" + run: echo "SDKMANRC_java=${{ env.SDKMANRC_java }}" | sed 's/-.*//' >> $GITHUB_ENV + - name: "☕️ Setup JDK" + uses: actions/setup-java@v5 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.SDKMANRC_java }} + - name: "Export gradle.properties properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + with: + build-scan-publish: ${{ env.ciBuildScanPublish }} + build-scan-terms-of-use-url: ${{ env.ciBuildScanTermsOfUseUrl }} + build-scan-terms-of-use-agree: ${{ env.ciBuildScanTermsOfUseAgree }} + - name: "⚙️ Run pre-release" + uses: apache/grails-github-actions/pre-release@asf + env: + RELEASE_VERSION: ${{ env.VERSION }} + - name: "📤 Publish to Maven Central" + env: + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: 'https://ossrh-staging-api.central.sonatype.com/service/local/' + NEXUS_PUBLISH_DESCRIPTION: '${{ env.REPO_NAME }}:${{ env.VERSION }}:${{ env.RUN_ID }}' + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} + run: > + ./gradlew + -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg + publishMavenPublicationToSonatypeRepository + closeSonatypeStagingRepository + - name: "Generate Build Date file" + run: echo "$SOURCE_DATE_EPOCH" >> build/BUILD_DATE.txt + - name: "Upload Build Date file" + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b + with: + files: build/BUILD_DATE.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + release: + name: "Make Release Files Available" + environment: release # this step will be delayed until approved + needs: [ publish ] + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: "📝 Establish release version" + run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV" + - name: "📥 Checkout repository" + uses: actions/checkout@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ env.TAG }} + - name: "Export .sdkmanrc properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + with: + file: ".sdkmanrc" + prefix: "SDKMANRC_" + - name: "Determine Java Version" + run: echo "SDKMANRC_java=${{ env.SDKMANRC_java }}" | sed 's/-.*//' >> $GITHUB_ENV + - name: "☕️ Setup JDK" + uses: actions/setup-java@v5 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.SDKMANRC_java }} + - name: "Export gradle.properties properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + with: + build-scan-publish: ${{ env.ciBuildScanPublish }} + build-scan-terms-of-use-url: ${{ env.ciBuildScanTermsOfUseUrl }} + build-scan-terms-of-use-agree: ${{ env.ciBuildScanTermsOfUseAgree }} + - name: "📤 Release staging repository" + env: + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: 'https://ossrh-staging-api.central.sonatype.com/service/local/' + NEXUS_PUBLISH_DESCRIPTION: '${{ env.REPO_NAME }}:${{ env.VERSION }}:${{ env.RUN_ID }}' + run: > + ./gradlew + findSonatypeStagingRepository + releaseSonatypeStagingRepository + docs: + environment: docs # this step will be delayed until approved + name: "Publish Documentation" + needs: [ publish, release ] + runs-on: ubuntu-24.04 + permissions: + contents: write # required to publish documentation to GitHub pages branches + steps: + - name: "📝 Establish release version" + run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV" + - name: "📥 Checkout repository" + uses: actions/checkout@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ env.TAG }} + - name: "Export .sdkmanrc properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + with: + file: ".sdkmanrc" + prefix: "SDKMANRC_" + - name: "Determine Java Version" + run: echo "SDKMANRC_java=${{ env.SDKMANRC_java }}" | sed 's/-.*//' >> $GITHUB_ENV + - name: "☕️ Setup JDK" + uses: actions/setup-java@v5 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.SDKMANRC_java }} + - name: "Export gradle.properties properties" + uses: apache/grails-github-actions/export-gradle-properties@asf + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v5 + with: + build-scan-publish: ${{ env.ciBuildScanPublish }} + build-scan-terms-of-use-url: ${{ env.ciBuildScanTermsOfUseUrl }} + build-scan-terms-of-use-agree: ${{ env.ciBuildScanTermsOfUseAgree }} + - name: "🔨 Build Documentation" + run: ./gradlew docs + - name: "🚀 Publish to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GRADLE_PUBLISH_RELEASE: 'true' + SOURCE_FOLDER: build/docs + VERSION: ${{ env.VERSION }} + close: + name: "To Next Version" + environment: close # this step will be delayed until approved + needs: [ publish, docs, release ] + runs-on: ubuntu-24.04 + permissions: + contents: write # required for gradle.properties revert + issues: write # required for milestone closing + pull-requests: write # to create the PR that will increment the version + steps: + - name: "📥 Checkout repository" + uses: actions/checkout@v6 + with: + ref: ${{ env.TAG }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: "⚙️ Run post-release" + uses: apache/grails-github-actions/post-release@asf diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..03e409c --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,3 @@ +java=17.0.18-librca +gradle=8.14.4 +groovy=4.0.30 diff --git a/.skills/gradle-best-practices.md b/.skills/gradle-best-practices.md new file mode 100644 index 0000000..58f8693 --- /dev/null +++ b/.skills/gradle-best-practices.md @@ -0,0 +1,254 @@ +# Gradle Best Practices + +## Purpose + +This skill covers Gradle best practices for this project, including convention plugins, extension configuration, +lazy APIs, and build structure. Convention plugins remove duplication across subprojects by centralizing shared +build logic. They live in the `build-logic/` composite build and are applied by ID in each subproject's `build.gradle`. + +## Core Rules + +### NEVER configure subprojects from the root build.gradle + +The root `build.gradle` must NEVER use `subprojects {}`, `allprojects {}`, or `configure(subprojects.matching {...}) {}` +to apply plugins or configure subproject behavior. This is an antipattern that causes ordering issues, breaks project +isolation, and makes builds harder to reason about. + +```groovy +// BAD - Never do this in root build.gradle +subprojects { + apply plugin: 'groovy' + dependencies { + implementation 'org.example:shared-lib:1.0' + } +} + +// BAD - Never do this either +allprojects { + repositories { + mavenCentral() + } +} +``` + +Instead, create a convention plugin in `build-logic/` and apply it in each subproject that needs it: + +```groovy +// GOOD - build-logic/src/main/groovy/config.compile.gradle +plugins { + id 'groovy' +} +// shared compilation config here +``` + +```groovy +// GOOD - plugin/build.gradle +plugins { + id 'config.compile' +} +``` + +The ONLY exception is the `root-publish.gradle` convention plugin, which exists solely as a workaround for a Nexus +publishing bug (https://github.com/gradle-nexus/publish-plugin/issues/310) that requires version/group to be set at the +root level. + +### Use the composite build pattern + +Convention plugins reside in `build-logic/`, which is included as a composite build via `settings.gradle`: + +```groovy +pluginManagement { + includeBuild('./build-logic') { + it.name = 'build-logic' + } +} +``` + +### Naming convention + +Convention plugin files follow the pattern: + +``` +build-logic/src/main/groovy/config..gradle +``` + +The plugin ID matches the filename (minus the `.gradle` extension). For example: + +- `config.compile.gradle` -> plugin ID `config.compile` + +### Declare external plugin dependencies in build-logic/build.gradle + +When a convention plugin applies a third-party plugin, that plugin must be declared as an `implementation` dependency in +`build-logic/build.gradle`: + +```groovy +// build-logic/build.gradle +plugins { + id 'groovy-gradle-plugin' +} + +dependencies { + implementation platform("org.apache.grails:grails-bom:${gradleProperties.grailsVersion}") + implementation 'org.apache.grails:grails-gradle-plugins' + implementation "com.adarshr:gradle-test-logger-plugin:${gradleProperties.testLoggerVersion}" + implementation 'cloud.wondrify:asset-pipeline-gradle' + implementation 'org.apache.grails.gradle:grails-publish' +} +``` + +### Share properties from root gradle.properties + +The `build-logic/build.gradle` reads the root `gradle.properties` and exposes those values as extra properties so +convention plugins can reference them (e.g., `grailsVersion`): + +```groovy +file('../gradle.properties').withInputStream { is -> + extensions.extraProperties.set( + 'gradleProperties', + new Properties().tap { load(is) } + ) +} + +allprojects { project -> + gradleProperties.stringPropertyNames().each { key -> + project.extensions.extraProperties.set( + key, + gradleProperties.getProperty(key) + ) + } +} +``` + +## Avoid Eager Initialization + +Always use lazy/deferred APIs to avoid eagerly resolving tasks or configurations: + +```groovy +// GOOD - lazy task configuration +tasks.withType(JavaCompile).configureEach { + options.encoding = StandardCharsets.UTF_8.name() +} + +tasks.named('bootRun', JavaExec) { + doFirst { /* ... */ } +} + +tasks.register('docs') { + dependsOn(/* ... */) +} + +// BAD - eager resolution +tasks.withType(JavaCompile) { // missing .configureEach + options.encoding = 'UTF-8' +} + +task docs { // old task() API is eager + dependsOn /* ... */ +} +``` + +Key APIs to use: + +- `tasks.register()` instead of `task()` +- `tasks.named()` instead of `tasks.getByName()` +- `tasks.withType(X).configureEach {}` instead of `tasks.withType(X) {}` +- `project.provider {}` for lazy values +- `layout.buildDirectory` instead of `buildDir` +- `dependsOn()` method instead of `dependsOn =` setter (setter replaces all dependencies; the method adds to them) +- Do NOT chain `.configure {}` on `tasks.register()` or `tasks.named()` — pass the closure directly to preserve type hints + +## Extension Configuration with Type Hints + +When configuring project extensions (like publishing metadata or third-party plugin configurations), use +`extensions.configure(Type)` with explicit `it` for type hints and better IDE support: + +```groovy +// GOOD - explicit it in extensions.configure() for type hints +extensions.configure(GrailsPublishExtension) { + it.artifactId = project.name + it.githubSlug = 'gpc/taggable' + it.license.name = 'Apache-2.0' + it.title = 'My Plugin' + it.developers = [name: 'Developer Name'] +} +``` + +Explicit `it` is NOT required in `tasks.named()`, `tasks.register()`, or `configureEach` — these already have typed +delegates: + +```groovy +// GOOD - no explicit it needed, delegate is already typed +tasks.withType(Checkstyle).configureEach { + group = 'verification' + onlyIf { !project.hasProperty('skipCodeStyle') } +} + +tasks.named('bootRun', JavaExec) { + doFirst { + jvmArgs("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005") + } +} +``` + +**Benefits of `extensions.configure(Type)` with explicit `it`:** + +- IDE auto-completion and type-checking for extension properties +- Clearer intent: code readers immediately see the extension type being configured +- Reduces runtime errors from typos in property names + +## Composition Over Inheritance + +Convention plugins should compose by applying other convention plugins rather than duplicating logic: + +```groovy +// example.gradle applies other convention plugins +plugins { + id 'org.apache.grails.gradle.grails-web' + id 'org.apache.grails.gradle.grails-gsp' + id 'config.grails-assets' + id 'config.app-run' +} +``` + +## Existing Convention Plugins + +| Plugin | Purpose | +|----------------------------------|--------------------------------------------------------------------------------------| +| `app-run.gradle` | Debug flags for `bootRun` | +| `code-coverage.gradle` | JaCoCo coverage for project (XML + HTML reports) | +| `code-coverage-aggregate.gradle` | JaCoCo coverage aggregation across subprojects (XML + HTML reports) | +| `code-style.gradle` | Checkstyle + CodeNarc code style checking (configs in `build-logic/config/`) | +| `compile.gradle` | Java/Groovy compilation settings (UTF-8, incremental, Java release from `.sdkmanrc`) | +| `docs.gradle` | Documentation aggregation (Groovydoc + Asciidoctor) | +| `example-app.gradle` | Example app config (grails-web, GSP, assets) | +| `grails-assets.gradle` | Asset pipeline with Bootstrap/jQuery WebJars | +| `grails-plugin.gradle` | Grails plugin application | +| `publish.gradle` | Per-project Maven publishing metadata | +| `publish-root.gradle` | Root-level Nexus publishing workaround | +| `testing.gradle` | Test framework config (Spock, JUnit Platform, test-logger) | + +## When to Create a New Convention Plugin + +Create a new convention plugin when: + +- Two or more subprojects share the same build configuration +- A subproject's `build.gradle` grows beyond applying plugins and declaring dependencies +- You need to enforce a project-wide standard (e.g., code formatting, static analysis) + +Keep each convention plugin focused on a single concern. Prefer small, composable plugins over monolithic ones. + +## Repository Management + +Repositories are managed centrally in `settings.gradle` via `dependencyResolutionManagement`: + +```groovy +dependencyResolutionManagement { + repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS + repositories { + maven { url = 'https://repo.grails.org/grails/restricted' } + } +} +``` + +This prevents subprojects from declaring their own repositories, ensuring consistency. The `FAIL_ON_PROJECT_REPOS` mode +enforces this. diff --git a/.skills/plugin-project.md b/.skills/plugin-project.md new file mode 100644 index 0000000..956ef87 --- /dev/null +++ b/.skills/plugin-project.md @@ -0,0 +1,145 @@ +# Plugin Project Best Practices + +## Purpose + +The `plugin/` directory contains the Grails plugin artifact. It is the only publishable library in this repository. It +must contain ONLY the plugin source code and unit tests; nothing else. + +## Core Rules + +### Plugin project contains ONLY plugin code and unit tests + +The plugin project (`plugin/`) must contain: + +- Plugin source code under `src/main/groovy/` and `grails-app/` +- Unit tests under `src/test/groovy/` +- Plugin configuration files under `grails-app/conf/` + +The plugin project must NOT contain: + +- Integration tests (these belong in example apps under `examples/`) +- Functional tests (these belong in example apps under `examples/`) +- Example controllers, views, or domain classes +- Test controllers or test-specific artifacts +- Application-level configuration not related to the plugin (e.g., database config, asset config) + +### Why: separation of concerns + +Keeping integration/functional tests out of the plugin project ensures: + +1. The plugin artifact is clean – no test dependencies or test code leaks into the published JAR +2. Tests that require a running Grails application exercise the plugin as a real consumer would +3. The plugin's API surface is validated from the outside, not the inside +4. Different example apps can test different configurations of the plugin + +## Project Structure + +``` +plugin/ +├── build.gradle # Only convention plugins + dependencies +├── grails-app/ +│ ├── conf/ +│ │ ├── application.yml # Plugin-specific config defaults +│ ├── controllers/ # Interceptors, controller-scoped artifacts +│ │ └── org/grails/plugins/servertiming/ +│ │ └── ServerTimingInterceptor.groovy +└── src/ + ├── main/groovy/ # Core plugin classes + │ └── org/grails/plugins/servertiming/ + │ ├── ServerTimingAutoConfiguration.groovy + │ ├── ServerTimingFilter.groovy + │ ├── ServerTimingGrailsPlugin.groovy + │ ├── ServerTimingResponseWrapper.groovy + │ ├── config/ + │ │ ├── EnabledCondition.groovy + │ │ └── ServerTimingConfig.groovy + │ └── core/ + │ ├── Metric.groovy + │ └── TimingMetric.groovy + ├── main/resources/ + │ ├── META-INF/spring + │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports + │ └── spring-configuration-metadata.json + └── test/groovy/ # Unit tests ONLY + └── org/grails/plugins/servertiming/ + ├── MetricSpec.groovy + └── TimingMetricSpec.groovy +``` + +## build.gradle Pattern + +The plugin's `build.gradle` should be minimal -- apply convention plugins and declare dependencies: + +```groovy +plugins { + id 'config.compile' + id 'config.testing' + id 'config.grails-plugin' + id 'config.publish' +} + +version = projectVersion +group = 'io.github.gpc' + +dependencies { + + profile 'org.apache.grails.profiles:web-plugin' + console 'org.apache.grails:grails-console' + + compileOnly platform("org.apache.grails:grails-bom:$grailsVersion") + compileOnly 'org.apache.grails:grails-dependencies-starter-web' + + testImplementation platform("org.apache.grails:grails-bom:$grailsVersion") + testImplementation 'org.apache.grails:grails-dependencies-starter-web' + testImplementation 'org.apache.grails:grails-dependencies-test' +} +``` + +Key patterns: + +- Use `compileOnly` for framework dependencies the consuming application will provide +- Use `testImplementation` for test-only dependencies +- Apply `config.publish` to configure Maven publishing metadata +- NEVER add custom task configuration here – move it to a convention plugin + +## Unit Test Guidelines + +Unit tests in the plugin project test individual classes in isolation: + +- Test domain logic, validation, and data structures (e.g., `Tag`, `TagLink`) +- Test utility classes and traits (e.g., `Taggable`) +- Use Spock Framework with `@Unroll` for data-driven tests +- Do NOT start the Grails application context for unit tests +- Do NOT make HTTP requests in unit tests +- Do NOT test controller actions, interceptors, or filters end-to-end in the plugin project + +### What belongs in unit tests + +- `Tag` name constraints (uniqueness, blank, length) +- `TagLink` polymorphic mapping validation +- `Taggable` trait instance methods (`addTag`, `removeTag`, `parseTags`, etc.) when isolated +- `TaggableService` helpers that do not require a running context +- Equals/hashCode contracts on domain entities +- Validation error cases + +### What does NOT belong in unit tests + +- Testing that `Taggable` static methods query correctly against a real datastore (integration test) +- Testing that `TaggableService.refreshDomainClasses()` wires up domain class families in a running app (integration test) +- Testing `` rendering in a GSP view (functional test) +- Testing behavior with a real Hibernate session, GSP views, JSON rendering, or static assets (functional test) + +## Plugin Descriptor + +The `TaggableGrailsPlugin` class extends `grails.plugins.Plugin` and exposes important +information about the plugin to the Grails framework. + +## Dependency Scoping + +- **`compileOnly`**: Framework dependencies the host app provides (Grails web, servlet API) +- **`implementation`**: Dependencies the plugin bundles and needs at runtime (use sparingly) +- **`testImplementation`**: Test framework dependencies (Spock, grails-testing-support) +- **`console`**: Grails console support +- **`profile`**: The Grails profile (web-plugin for plugins) + +Avoid `implementation` for Grails/Spring/Servlet dependencies -- the consuming application provides these. diff --git a/.skills/repository-structure.md b/.skills/repository-structure.md new file mode 100644 index 0000000..c07cbb8 --- /dev/null +++ b/.skills/repository-structure.md @@ -0,0 +1,216 @@ +# Grails Plugin Repository Structure + +## Purpose + +This document defines the canonical structure for Grails plugin repositories. The structure enforces separation of +concerns: the plugin project contains only library code and unit tests, example apps provide integration/functional test +coverage, and build logic is centralized in convention plugins. + +## Directory Layout + +``` +taggable/ +├── .github/ # CI/CD workflows and GitHub config +│ ├── workflows/ +│ │ ├── ci.yml # Build, test, publish snapshots +│ │ ├── code-coverage.yml # Create a code coverage report +│ │ ├── code-style.yml # Check code style +│ │ ├── release.yml # Multi-stage release pipeline +│ │ └── release-notes.yml # Automated release draft notes +│ ├── release-drafter.yml # Release drafter categories/labels +│ └── dependency-graph/ +│ └── external-references.yml # Maven Central package association +│ +├── build-logic/ # Gradle convention plugins (composite build) +│ ├── build.gradle # Plugin dependencies (groovy-gradle-plugin) +│ ├── settings.gradle # Build-logic project settings +│ ├── config/ # Shared code style config files +│ │ ├── checkstyle/ # Checkstyle XML configs +│ │ └── codenarc/ # CodeNarc ruleset +│ └── src/main/groovy/ # Convention plugin files (*.gradle) +│ ├── config.app-run.gradle +│ ├── config.code-coverage.gradle +│ ├── config.code-coverage-aggregate.gradle +│ ├── config.code-style.gradle +│ ├── config.compile.gradle +│ ├── config.docs.gradle +│ ├── config.example-app.gradle +│ ├── config.grails-assets.gradle +│ ├── config.grails-plugin.gradle +│ ├── config.publish.gradle +│ ├── config.publish-root.gradle +│ └── config.testing.gradle +│ +├── plugin/ # The Grails plugin artifact +│ ├── build.gradle # Convention plugins + dependencies only +│ ├── grails-app/ +│ │ ├── conf/ # Plugin config (application.yml, logback) +│ │ └── controllers/ # Interceptors and controller artifacts +│ └── src/ +│ ├── main/groovy/ # Plugin source code +│ └── test/groovy/ # Unit tests ONLY +│ +├── examples/ # Example apps (auto-discovered) +│ ├── app1/ # first example app with the plugin enabled +│ │ ├── build.gradle +│ │ ├── grails-app/ # Standard Grails app structure +│ │ │ ├── conf/ +│ │ │ ├── controllers/ # Test controllers +│ │ │ ├── views/ # Test views (GSP) +│ │ │ ├── init/ +│ │ │ ├── assets/ +│ │ │ └── i18n/ +│ │ └── src/ +│ │ └── integration-test/ # Integration & functional tests +│ └── app2/ # second app showing disable feature +│ ├── build.gradle +│ ├── grails-app/ # Standard Grails app structure +│ │ ├── conf/ +│ │ ├── controllers/ # Test controllers +│ │ ├── views/ # Test views (GSP) +│ │ ├── init/ +│ │ ├── assets/ +│ │ └── i18n/ +│ └── src/ +│ └── integration-test/ # Integration & functional tests +│ +├── code-coverage/ # JaCoCo coverage aggregation +│ └── build.gradle # Declares which projects contribute coverage data +│ +├── docs/ # Asciidoctor documentation +│ ├── build.gradle +│ └── src/docs/ # .adoc source files +│ +├── build.gradle # Root build (docs + root-publish ONLY) +├── settings.gradle # Multi-project settings + composite build +├── gradle.properties # Shared version properties +├── .sdkmanrc # SDK versions (Java, Gradle, Groovy) +├── AGENTS.md # AI agent instructions +├── .skills/ # Best practice skill files +├── LICENSE # Apache 2.0 +└── README.md +``` + +## Key Architectural Rules + +### 1. Root build.gradle is minimal + +The root `build.gradle` applies only root-level convention plugins (docs aggregation, root-publish workaround). It must +NEVER use `subprojects {}`, `allprojects {}`, or any mechanism to configure child projects. All shared configuration +flows through convention plugins. + +```groovy +// Root build.gradle -- this is all that should be here +plugins { + id 'idea' + id 'config.docs' + id 'config.root-publish' +} +``` + +### 2. Plugin project = library code + unit tests + +The `plugin/` project is the published artifact. It contains: + +- Source code (`src/main/groovy/`, `grails-app/`) +- Unit tests (`src/test/groovy/`) + +It does NOT contain integration tests, functional tests, example controllers, or test views. + +### 3. Example apps = integration/functional tests + +All tests requiring a running Grails application live in example apps under `examples/`. Each app: + +- Depends on the plugin via `implementation project(':taggable')` +- Contains test controllers and views that exercise the plugin +- Contains integration tests under `src/integration-test/` +- Is auto-discovered by `settings.gradle` + +### 4. Build logic is centralized + +Convention plugins in `build-logic/` eliminate all duplication: + +- Compilation settings: `config.compile.gradle` +- Test configuration: `config.testing.gradle` +- Plugin setup: `config.grails-plugin.gradle` +- Example app setup: `config.example-app.gradle` +- Publishing: `config.publish.gradle` +- Coverage aggregation: `config.coverage-aggregate.gradle` +- Code style checking: `config.code-style.gradle` + +### 5. Centralized dependency resolution + +Repositories are declared once in `settings.gradle` using `dependencyResolutionManagement`. The `FAIL_ON_PROJECT_REPOS` +mode prevents subprojects from declaring their own repositories. + +### 6. Shared properties via gradle.properties + +Version numbers and shared settings live in `gradle.properties` at the root: + +```properties +projectVersion=0.0.1-SNAPSHOT +grailsVersion=7.0.7 +``` + +These are available in all subprojects as project properties (`projectVersion`, `grailsVersion`). + +## Adding a New Example App + +1. Create a new directory under `examples/` (e.g., `examples/app2/`) +2. Add a `build.gradle` applying the convention plugins: + ```groovy + plugins { + id 'config.example-app' + } + ``` +3. Add standard Grails app structure under `grails-app/` +4. Add integration tests under `src/integration-test/groovy/` +5. The app will be auto-discovered by `settings.gradle` and automatically included in coverage aggregation -- no manual + registration needed + +## Adding a New Convention Plugin + +1. Create a new file: `build-logic/src/main/groovy/config..gradle` +2. If the plugin applies third-party plugins, add their dependencies to `build-logic/build.gradle` +3. Apply the new plugin ID in the relevant subproject(s) +4. Keep the plugin focused on a single concern + +## Build Commands + +```bash +# Full build (all subprojects) +./gradlew build + +# Plugin unit tests only +./gradlew :taggable:test + +# Example app integration tests +./gradlew :app1:integrationTest + +# Aggregated coverage report (unit + integration) +./gradlew jacocoAggregatedReport + +# Run an example app +./gradlew :app1:bootRun + +# Generate documentation +./gradlew docs + +# Clean everything +./gradlew clean + +# Skip tests +./gradlew build -PskipTests +``` + +## SDK Management + +The `.sdkmanrc` file pins exact SDK versions. Run `sdk env install` to install them. CI reads `.sdkmanrc` to determine +the Java version dynamically. + +## CI/CD Pipeline + +- **CI**: Builds and tests on every push/PR. Publishes snapshots on push to release branches. +- **Coverage**: Runs the full build and posts an aggregated JaCoCo coverage summary to the GitHub Actions job summary. +- **Release**: 4-stage pipeline (stage -> release -> docs -> version bump) triggered by GitHub release. +- **Release Notes**: Auto-drafts release notes from PRs using release-drafter with category labels. diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 48b31d6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: groovy -jdk: -- oraclejdk7 -before_script: -- rm -rf target -script: ./travis-build.sh -env: - global: - - GIT_NAME="Graeme Rocher" - - GIT_EMAIL="graeme.rocher@gmail.com" - - secure: nDFWKPneybRf6eS5g/UimKxizX7Z/EyJgq6Zz++OIfENuLTK5ZfXNgeCB0E3CVQ/EsO1pZIi6rJz27s9sDeneBgmBdoKu4hAIVHk5jwL26SKFd1G3zy9+JHEGrYlgC4pcxKydWuhm7euqJT2ighuSlSIPjcSR9ep+87moHLv3NY= - - secure: MdCohHHlR+BT0YSkyqddr7ejNCwWNeg0epuPkCqoFRczOdNnT+TGeWrhxMzQVYPNpe2QG76sqrWwyGtJRAn6/Gch7bYfpHkoGWWsacm2VGXLPXucjyz2vVg+ZzWGirkssFfFVJsG33mljzhxjljGviKDgsVS2DYwRBO9OKQYqXY= - - secure: QOozG0LmSnIeslpKao6iudVCZ/MLGoI1nkELLDMer1EkZMOOD2jjWuTI1OvGeD2RKzeS2e78iyvXm5ohSSoAVqZ+IAKdG0SQWw/vXNUJV1PkgPO57DNnp5iyGwVh9vK+Q+iUy9veoRiaX91aWL5JzXobjHOxf7YcCjnYMZ/fTuU= - - secure: DOUGG6+sqQWYJlc8oa2Faio2lgMhU+gOo0eAGpn20omf0v/+4Wa/YqmaqW77TM0bRxtx0Qt1S8f2R4RfSum2M58Kn5FuNJPOFSycpRdUv7A5tmJ+aNtCUpx2oBOp4q31exQH4K3KPRidVE7DQDMPX7KkvvLD4G9C6V6qZUQ4SAY= - - secure: Pr2loG4v2x4xH/+K8NImt+Geo3wT7chY4tiCOXWnuqID/jU2CIRoEpk8df3YS3S2MtStnDEMMDWgMx+3rfCURMEGlr9Nd7z5XkukcgnWpbB4YEm2vx/B37cGhGmbeGJdQTanPvcG8HyoJuBlkhUEAFJ6kNtfcUZWnmtIj7/EfCM= -sudo: false -cache: - directories: - - $HOME/.gradle diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..29a9231 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,202 @@ +# AGENTS.md - taggable + +## Project Overview + +This is a **Grails Plugin** that adds a generic tagging mechanism to any Grails domain class. Domain classes implement +the `Taggable` trait to gain instance and static methods for attaching, querying, and counting tags, plus a taglib for +rendering a frequency-ranked tag cloud. + +- **Language:** Groovy 4.0.30 on Java 17 +- **Framework:** Grails 7.x +- **Build System:** Gradle 8.14.4 (with wrapper) +- **Current Version:** 7.0.x-SNAPSHOT +- **License:** Apache 2.0 + +## Skill Files (Best Practices) + +Detailed best practices are documented in `.skills/`: + +| Skill File | Purpose | +|------------------------------------------------------------------------|-------------------------------------------------------| +| [`.skills/repository-structure.md`](.skills/repository-structure.md) | Canonical directory layout and architectural rules | +| [`.skills/gradle-best-practices.md`](.skills/gradle-best-practices.md) | Gradle best practices, convention plugins, and idioms | +| [`.skills/plugin-project.md`](.skills/plugin-project.md) | Plugin project scope: source code + unit tests only | +| [`.skills/example-apps.md`](.skills/example-apps.md) | Example app patterns: integration & functional tests | + +**Read these skill files before making structural changes to the repository.** + +## Critical Rules + +1. **NEVER add code to the root `build.gradle` to configure subprojects.** No `subprojects {}`, `allprojects {}`, or + `configure()` blocks. All shared configuration goes through convention plugins in `build-logic/`. +2. **The plugin project contains ONLY plugin code and unit tests.** No integration tests, no functional tests, no + example controllers or views. +3. **Example apps under `examples/` host all integration and functional tests.** They depend on the plugin via + `implementation project(':taggable')` and test it as a real consumer would. +4. **Use Gradle convention plugins to deduplicate.** If two or more subprojects share build logic, extract it into a + convention plugin in `build-logic/`. +5. **Always use lazy Gradle APIs** to avoid eager initialization (`tasks.register()`, `tasks.named()`, `configureEach`, + `provider {}`). + +## Repository Structure + +``` +taggable/ +├── .skills/ # Best practice skill files +├── plugin/ # Core Grails plugin (artifact: taggable) +│ ├── grails-app/ # Plugin domain classes, services, and taglibs +│ └── src/main/ # Plugin source code (Taggable trait, plugin descriptor) +├── examples/app1/ # Example Grails app +│ └── grails-app/ # Domain classes and conf for integration testing +├── docs/ # Asciidoctor documentation +├── build-logic/ # Gradle convention plugins (composite build) +├── .github/workflows/ # CI, release, and release-notes workflows +├── build.gradle # Root build file (docs + root-publish ONLY) +├── settings.gradle # Multi-project settings +└── gradle.properties # Version properties +``` + +## Build and Test Commands + +```bash +# Full build (compile + test) +./gradlew build + +# Run only unit tests (plugin module) +./gradlew :taggable:test + +# Run integration tests (example app) +./gradlew :app1:integrationTest + +# Skip tests +./gradlew build -PskipTests + +# Run the example app +./gradlew :app1:bootRun + +# Generate documentation +./gradlew docs + +# Clean build +./gradlew clean build + +# Run code style checks only +./gradlew codeStyle + +# Skip code style checks +./gradlew build -PskipCodeStyle +``` + +## SDK Requirements + +Use SDKMAN to install the correct tool versions (see `.sdkmanrc`): + +- Java: `17.0.18-librca` +- Gradle: `8.14.4` +- Groovy: `4.0.30` + +Run `sdk env install` to set up the environment. + +## Architecture + +The plugin provides a trait-based tagging mechanism with a supporting service, domain model, and tag library: + +1. **`TaggableGrailsPlugin`** is the plugin descriptor. It extends `grails.plugins.Plugin`, observes `hibernate`, and + calls `taggableService.refreshDomainClasses()` on startup and `onChange` so the domain class family map stays in + sync with reloaded artifacts. +2. **`Taggable`** is a Groovy trait that domain classes implement to become taggable. It provides instance methods + like `addTag(name)`, `addTags(names)`, `removeTag(name)`, `setTags(list)`, `parseTags(str, delimiter)`, and + `getTags()`, plus static methods `findAllByTag(name)`, `findAllByTagWithCriteria(name, closure)`, + `countByTag(name)`, `getAllTags()`, `getTotalTags()`, and `findAllTagsWithCriteria(params, closure)`. +3. **`TaggableService`** maintains `domainClassFamilies` — a map from class name to the property names of that class + plus all of its subclasses — so polymorphic queries against `TagLink` can include subclass references. It exposes + `getTagCounts(type)` for aggregation and `refreshDomainClasses()` which is called by the plugin lifecycle. +4. **`Tag`** and **`TagLink`** are the domain entities. `Tag` stores the unique tag name; `TagLink` is the polymorphic + join table with `tag`, `tagRef` (the id of the tagged instance), and `type` (the simple class name of the tagged + domain class). +5. **`TagsTagLib`** (namespace `tags`) provides `` for rendering a frequency-ranked tag cloud where CSS + class names scale with tag frequency. + +### Core Classes + +| Class / Interface | Location | Purpose | +|-------------------------|-------------------------------------------------------|--------------------------------------------------------------------| +| `TaggableGrailsPlugin` | `plugin/src/main/groovy/grails/plugins/taggable/` | Plugin descriptor; refreshes domain class families on load/change | +| `Taggable` | `plugin/src/main/groovy/grails/plugins/taggable/` | Trait that adds tagging methods to a domain class | +| `TagException` | `plugin/src/main/groovy/grails/plugins/taggable/` | Thrown when a tag is in an invalid state | +| `Tag` | `plugin/grails-app/domain/grails/plugins/taggable/` | Domain entity storing the unique tag name | +| `TagLink` | `plugin/grails-app/domain/grails/plugins/taggable/` | Polymorphic join entity (`tag`, `tagRef`, `type`) | +| `TaggableService` | `plugin/grails-app/services/grails/plugins/taggable/` | Maintains `domainClassFamilies`; exposes `getTagCounts(type)` | +| `TagsTagLib` | `plugin/grails-app/taglib/grails/plugins/taggable/` | Tag library: `` | + +## Configuration + +The plugin reads the following keys from the application config (for example, `application.yml` or `application.groovy`): + +| Key | Type | Default | Purpose | +|------------------------------------|--------------|--------------------------------------------------------|-----------------------------------------------------------------| +| `grails.taggable.preserve.case` | boolean | `false` | When `false`, tag names are normalized to lowercase | +| `grails.taggable.tag.table` | string | (Hibernate default) | Overrides the physical table name for `Tag` | +| `grails.taggable.tagLink.table` | string | (Hibernate default) | Overrides the physical table name for `TagLink` | +| `grails.taggable.tag.autoImport` | boolean | (Hibernate default) | Toggle Hibernate `autoImport` for `Tag` | +| `grails.taggable.tagLink.autoImport` | boolean | (Hibernate default) | Toggle Hibernate `autoImport` for `TagLink` | +| `grails.taggable.css.classes` | list | `['smallest','small','medium','large','largest']` | CSS classes used by `` to scale by frequency | + +Example `application.groovy`: + +```groovy +grails { + taggable { + preserve.case = false + css.classes = ['tag-xs', 'tag-sm', 'tag-md', 'tag-lg', 'tag-xl'] + tag.table = 'my_tag' + tagLink.table = 'my_tag_link' + } +} +``` + +## Testing + +### Unit Tests (`plugin/src/test/`) + +Unit tests use the **Spock Framework** and run on JUnit Platform. + +### Integration / Functional Tests (`examples/app1/`) + +The example app under `examples/app1/` declares taggable domain classes and exercises the `Taggable` trait, the +`TaggableService`, and the `` taglib against a real datastore. Integration and functional tests added +here depend on the plugin as a real consumer would. + +## Build-Logic Convention Plugins + +Convention plugins in `build-logic/src/main/groovy/` standardize build configuration: + +| Plugin | Purpose | +|--------------------------|--------------------------------------------------------------------------------------| +| `app-run.gradle` | Debug flags for `bootRun` | +| `compile.gradle` | Java/Groovy compilation settings (UTF-8, incremental, Java release from `.sdkmanrc`) | +| `docs.gradle` | Documentation aggregation (Groovydoc + Asciidoctor) | +| `example-app.gradle` | Example app config (grails-web, GSP, assets) | +| `grails-assets.gradle` | Asset pipeline with Bootstrap/jQuery WebJars | +| `grails-plugin.gradle` | Grails plugin application | +| `publish.gradle` | Per-project Maven publishing metadata | +| `publish-root.gradle` | Root-level Nexus publishing workaround | +| `testing.gradle` | Test framework config (Spock, JUnit Platform, test-logger) | + +## CI/CD + +- **CI** (`.github/workflows/ci.yml`): Builds and tests on push/PR; publishes snapshots to Maven Central Snapshots on + push to release branches. +- **Release** (`.github/workflows/release.yml`): 4-stage pipeline triggered by GitHub release — stage artifacts, release + to Maven Central, publish docs to GitHub Pages, bump version. +- **Release Notes** (`.github/workflows/release-notes.yml`): Auto-drafts release notes using release-drafter with + category labels. + +## Code Conventions + +- Groovy source files use standard Grails conventions (services, domain classes, and taglibs in `grails-app/`, other + classes in `src/main/groovy/`). +- **Use `def` for local variables** where the type is inferred from the right-hand side (e.g., constructor calls, + method calls, casts, factory methods). Explicit types should only be used for local variables when the type cannot + be inferred or when needed for `@CompileStatic` compilation. This applies to both production code and tests. +- When writing Gradle, always use the latest best practices to avoid eager initialization. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..129720c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,163 @@ +# Contributing to Taggable + +Thank you for your interest in contributing! This guide will help you get started. + +## Code of Conduct + +This project has a [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold it. Please report +unacceptable behavior to the project maintainers. + +## Getting Started + +### Prerequisites + +Install [SDKMAN!](https://sdkman.io/) to manage JDK, Gradle, and Groovy versions: + +```bash +curl -s "https://get.sdkman.io" | bash +``` + +### Setting Up the Development Environment + +```bash +# Clone the repository +git clone https://github.com/gpc/taggable.git +cd taggable + +# Install the required SDK versions (Java 17, Gradle 8.14.4, Groovy 4.0.30) +sdk env install + +# Build the project +./gradlew build +``` + +### Project Structure + +``` +taggable/ +├── plugin/ # The publishable Grails plugin (source + unit tests ONLY) +├── examples/app1/ # Example app with integration tests +├── build-logic/ # Gradle convention plugins (shared build configuration) +├── docs/ # Asciidoctor documentation +└── .skills/ # AI agent best-practice docs +``` + +Key architectural rules: + +- **Plugin module** contains only plugin source code and unit tests – no integration tests, no example controllers. +- **Example apps** under `examples/` host all integration and functional tests. They depend on the plugin as a real + consumer would. +- **Convention plugins** in `build-logic/` deduplicate build configuration. Never use `subprojects {}`, + `allprojects {}`, or `configure()` blocks in the root `build.gradle`. + +## Building and Testing + +```bash +# Full build (compile + all tests) +./gradlew build + +# Plugin unit tests only +./gradlew :taggable:test + +# Integration tests (runs the example app) +./gradlew :app1:integrationTest + +# Run the example app locally +./gradlew :app1:bootRun + +# Generate documentation +./gradlew docs + +# Skip tests +./gradlew build -PskipTests + +# Clean build +./gradlew clean build +``` + +### Code Coverage + +The project uses JaCoCo to aggregate coverage data from both plugin unit tests and example app integration tests. + +```bash +# Generate the aggregated coverage report +./gradlew jacocoAggregatedReport +``` + +Reports are generated at: + +| Report | Location | +|---------------------------------|----------------------------------------------------------------------------------| +| Aggregated (unit + integration) | `code-coverage/build/reports/jacoco/jacocoAggregatedReport/html/index.html` | +| Plugin unit tests | `plugin/build/reports/jacoco/test/html/index.html` | +| App1 integration tests | `examples/app1/build/reports/jacoco/jacocoIntegrationTestReport/html/index.html` | + +The aggregated report is also produced automatically as part of `./gradlew build`, so you can view it after any full +build. + +## Making Changes + +### Branching Strategy + +- Create a feature branch from the current release branch (e.g., `7.0.x`): + - `feature/short-description` for new features + - `fix/short-description` for bug fixes + - `docs/short-description` for documentation changes + - `refactor/short-description` for refactoring + +These branch prefixes are used by [release-drafter](https://github.com/release-drafter/release-drafter) to automatically +categorize changes in release notes. + +### Coding Standards + +- **Language:** Groovy 4.0 on Java 17 +- **Framework:** Grails 7.0 +- **Testing:** Spock Framework on JUnit Platform +- Follow existing code conventions in the project +- Keep the `Taggable` trait API backwards compatible — applications rely on the instance and static methods it adds to + their domain classes +- Preserve polymorphic `TagLink` semantics: any change to how `type` / `tagRef` is resolved must be reflected in + `TaggableService.domainClassFamilies` + +### Gradle Conventions + +- Always use lazy APIs: `tasks.register()`, `tasks.named()`, `configureEach`, `provider {}` +- Never use eager task creation (`tasks.create()`, `project.task()`) +- If two or more subprojects share build logic, extract it into a convention plugin in `build-logic/` + +## Submitting a Pull Request + +1. **Ensure all tests pass** locally: `./gradlew build` +2. **Write tests** for new functionality: + - Unit tests go in `plugin/src/test/` + - Integration tests go in `examples/app1/src/integration-test/` +3. **Update documentation** if you changed behavior or added features (in `docs/src/docs/`) +4. **Push your branch** and open a pull request against the release branch +5. **Fill out the PR template** completely + +### What to Expect + +- CI will run automatically on your PR +- A maintainer will review your changes +- You may be asked to make revisions +- Once approved, a maintainer will merge your PR + +## Reporting Issues + +- Use + the [bug report template](https://github.com/gpc/taggable/issues/new?template=bug_report.yml) + for bugs +- Use + the [feature request template](https://github.com/gpc/taggable/issues/new?template=feature_request.yml) + for enhancements +- Check [existing issues](https://github.com/gpc/taggable/issues) before creating a new one + +## Security Vulnerabilities + +If you discover a security vulnerability, **do not open a public issue**. Please see [SECURITY.md](.github/SECURITY.md) +for responsible disclosure instructions. + +## License + +By contributing to this project, you agree that your contributions will be licensed under +the [Apache License 2.0](LICENSE). diff --git a/README.md b/README.md index 67f0fdf..4f40c96 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,17 @@ Classes can be made taggable by implementing the [grails.plugins.taggable.Taggab Requirements ------------ -Grails Version: 4.0.0 +Grails 7.0.x, Groovy 4.0.x, Java 17+ Installation ------------ -Add this dependency to `build.gradle` +Add this dependency to `build.gradle`: ```groovy - dependencies { - compile "io.github.gpc:taggable:4.0.0" +dependencies { + implementation 'io.github.gpc:taggable:7.0.0' } - ``` By default, the plugin will force all tags to lower case. If you want to preserve the case of tags, you must specify the diff --git a/build-logic/build.gradle b/build-logic/build.gradle new file mode 100644 index 0000000..746607d --- /dev/null +++ b/build-logic/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'groovy-gradle-plugin' +} + +file('../gradle.properties').withInputStream { is -> + extensions.extraProperties.set( + 'gradleProperties', + new Properties().tap { load(is) } + ) +} + +allprojects { project -> + gradleProperties.stringPropertyNames().each { key -> + project.extensions.extraProperties.set( + key, + gradleProperties.getProperty(key) + ) + } +} + +dependencies { + implementation platform("org.apache.grails:grails-bom:${gradleProperties.grailsVersion}") + implementation 'cloud.wondrify:asset-pipeline-gradle' + implementation "com.adarshr:gradle-test-logger-plugin:${gradleProperties.testLoggerVersion}" + implementation 'org.apache.grails:grails-gradle-plugins' + implementation 'org.apache.grails.gradle:grails-publish' + implementation "org.asciidoctor.jvm.convert:org.asciidoctor.jvm.convert.gradle.plugin:${gradleProperties.asciidoctorVersion}" +} + diff --git a/build-logic/settings.gradle b/build-logic/settings.gradle new file mode 100644 index 0000000..d712464 --- /dev/null +++ b/build-logic/settings.gradle @@ -0,0 +1,13 @@ +import org.gradle.api.initialization.resolve.RepositoriesMode + +rootProject.name = 'build-logic' + +dependencyResolutionManagement { + repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS + repositories { + if (System.getenv('INCLUDE_MAVEN_LOCAL')) { + mavenLocal() + } + maven { url = 'https://repo.grails.org/grails/restricted' } + } +} diff --git a/build-logic/src/main/groovy/config.app-run.gradle b/build-logic/src/main/groovy/config.app-run.gradle new file mode 100644 index 0000000..8363425 --- /dev/null +++ b/build-logic/src/main/groovy/config.app-run.gradle @@ -0,0 +1,12 @@ +pluginManager.withPlugin('org.springframework.boot') { + tasks.named('bootRun', JavaExec) { + doFirst { + if (project.hasProperty('debugWait')) { + jvmArgs('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005') + } + if (project.hasProperty('debug')) { + jvmArgs('-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005') + } + } + } +} diff --git a/build-logic/src/main/groovy/config.compile.gradle b/build-logic/src/main/groovy/config.compile.gradle new file mode 100644 index 0000000..457667b --- /dev/null +++ b/build-logic/src/main/groovy/config.compile.gradle @@ -0,0 +1,65 @@ +import java.nio.charset.StandardCharsets + +plugins { + id 'groovy' +} + +tasks.withType(JavaCompile).configureEach { + options.with { + compilerArgs.add('-parameters') + encoding = StandardCharsets.UTF_8.name() + fork = true + incremental = true + release.set(resolveSdkmanJavaMajor(project)) + } + options.forkOptions.with { + jvmArgs.add('-Xmx1g') + memoryMaximumSize = '1g' + } +} + +tasks.withType(GroovyCompile).configureEach { + options.with { + compilerArgs.add('-parameters') + encoding = StandardCharsets.UTF_8.name() + fork = true + incremental = true + } + groovyOptions.with { + encoding = StandardCharsets.UTF_8.name() + optimizationOptions.indy = false + parameters = true + } + groovyOptions.forkOptions.with { + memoryMaximumSize = '1g' + jvmArgs.add('-Xmx1g') + } +} + +private static Provider resolveSdkmanJavaMajor(Project project) { + project.providers.provider { + def sdkmanrc = project.rootProject.file('.sdkmanrc') + if (!sdkmanrc.exists()) { + throw new GradleException('Missing .sdkmanrc in root project') + } + + def props = new Properties() + sdkmanrc.withInputStream { props.load(it) } + + def raw = props.getProperty('java')?.trim() + if (!raw) { + throw new GradleException('Missing java version in root project .sdkmanrc') + } + + def major = raw.tokenize('.').first() + if (!(major ==~ /\d+/)) { + throw new GradleException( + "Invalid java version '$raw' in root project .sdkmanrc (major '$major' is not an integer)" + ) + } + + return major.toInteger() + + } as Provider +} + diff --git a/build-logic/src/main/groovy/config.docs.gradle b/build-logic/src/main/groovy/config.docs.gradle new file mode 100644 index 0000000..712bff8 --- /dev/null +++ b/build-logic/src/main/groovy/config.docs.gradle @@ -0,0 +1,110 @@ +def docProject = provider { + project(":${project.name - 'root'}docs") +} +def pluginProject = provider { + project(":${project.name - '-root'}") +} + +tasks.register('cleanDocs', Delete) { + description = 'Deletes the documentation output' + group = 'documentation' + + delete(rootProject.layout.projectDirectory.dir('build/docs')) +} + +tasks.register('aggregateGroovyApiDoc', Groovydoc) { + description = 'Generates Groovy API Documentation for the plugin project under build/docs/gapi' + group = 'documentation' + + def upstream = pluginProject.flatMap { + it.tasks.named('groovydoc', Groovydoc) + } as Provider + + dependsOn(tasks.named('cleanDocs')) + dependsOn(upstream) + + access = GroovydocAccess.PROTECTED + includeAuthor = false + includeMainForScripts = true + processScripts = true + exclude('**/Application.groovy') + + + source = { upstream.get().source } + destinationDir = rootProject.layout.buildDirectory.dir('docs/gapi').get().asFile + classpath = files({ upstream.get().classpath }) + groovyClasspath = files({ upstream.get().groovyClasspath }) +} + +tasks.register('docs') { + description = 'Generates the documentation' + group = 'documentation' + + dependsOn( + 'aggregateGroovyApiDoc', + docProject.get().tasks.named('asciidoctor') + ) + finalizedBy( + 'copyAsciiDoctorDocs', + 'ghPagesRootIndexPage' + ) +} + +tasks.register('copyAsciiDoctorDocs', Copy) { + group = 'documentation' + + from(docProject.flatMap { it.layout.buildDirectory }) + into(rootProject.layout.buildDirectory) + include('docs/**') + includeEmptyDirs = false + +} + +tasks.register('ghPagesRootIndexPage') { + description = 'Provides a root index page with historical versions fetched from the gh-pages branch at build time' + group = 'documentation' + + def templateFile = docProject.map { it.layout.projectDirectory.file('src/docs/index.tmpl') } + def outputFile = rootProject.layout.buildDirectory.file('docs/ghpages.html') + + inputs.file(templateFile) + outputs.file(outputFile) + + doLast { + def githubUser = rootProject.findProperty('githubUser') as String + def githubProject = rootProject.findProperty('githubProject') as String + + List versions = [] + try { + def conn = URI.create("https://api.github.com/repos/${githubUser}/${githubProject}/contents/?ref=gh-pages").toURL().openConnection() + conn.setRequestProperty('Accept', 'application/vnd.github+json') + conn.setRequestProperty('User-Agent', 'gradle-docs-build') + def parsed = new groovy.json.JsonSlurper().parse(conn.inputStream) + versions = (parsed as List) + .findAll { it.type == 'dir' } + .collect { it.name as String } + .findAll { it ==~ /\d+\.\d+\.(\d+|x)(-.*)?/ } + .sort() + .reverse() + } catch (Exception e) { + logger.warn("ghPagesRootIndexPage: could not fetch GitHub versions — ${e.message}") + } + + String optionsHtml = versions + ? versions.collect { v -> "" }.join('\n ') + : '' + + def tokens = [ + '@OTHER_VERSIONS_OPTIONS@': optionsHtml, + '@GITHUB_REPO_URL@' : "https://github.com/${githubUser}/${githubProject}", + '@GITHUB_ORG_URL@' : "https://github.com/${githubUser}", + '@REPO_SLUG@' : githubProject, + ] + + def out = outputFile.get().asFile + out.parentFile.mkdirs() + def content = templateFile.get().asFile.text + tokens.each { token, value -> content = content.replace(token, value) } + out.text = content + } +} diff --git a/build-logic/src/main/groovy/config.example-app.gradle b/build-logic/src/main/groovy/config.example-app.gradle new file mode 100644 index 0000000..9d5446e --- /dev/null +++ b/build-logic/src/main/groovy/config.example-app.gradle @@ -0,0 +1,8 @@ +plugins { + id 'config.app-run' + id 'config.compile' + id 'config.grails-assets' + id 'config.testing' + id 'org.apache.grails.gradle.grails-web' + id 'org.apache.grails.gradle.grails-gsp' +} diff --git a/build-logic/src/main/groovy/config.grails-assets.gradle b/build-logic/src/main/groovy/config.grails-assets.gradle new file mode 100644 index 0000000..57b9381 --- /dev/null +++ b/build-logic/src/main/groovy/config.grails-assets.gradle @@ -0,0 +1,26 @@ +import asset.pipeline.gradle.AssetPipelineExtension + +plugins { + id 'cloud.wondrify.asset-pipeline' +} + +dependencies { + add('assetDevelopmentRuntime', 'org.webjars.npm:bootstrap') + add('assetDevelopmentRuntime', 'org.webjars.npm:bootstrap-icons') + add('assetDevelopmentRuntime', 'org.webjars.npm:jquery') +} + +extensions.configure(AssetPipelineExtension) { + it.excludes = [ + 'webjars/jquery/**', + 'webjars/bootstrap/**', + 'webjars/bootstrap-icons/**' + ] + it.includes = [ + 'webjars/jquery/*/dist/jquery.js', + 'webjars/bootstrap/*/dist/js/bootstrap.bundle.js', + 'webjars/bootstrap/*/dist/css/bootstrap.css', + 'webjars/bootstrap-icons/*/font/bootstrap-icons.css', + 'webjars/bootstrap-icons/*/font/fonts/*', + ] +} diff --git a/build-logic/src/main/groovy/config.grails-plugin.gradle b/build-logic/src/main/groovy/config.grails-plugin.gradle new file mode 100644 index 0000000..26ddbf4 --- /dev/null +++ b/build-logic/src/main/groovy/config.grails-plugin.gradle @@ -0,0 +1,16 @@ +import asset.pipeline.gradle.AssetPipelineExtension +import org.grails.gradle.plugin.core.GrailsExtension + +plugins { + id 'org.apache.grails.gradle.grails-plugin' + id 'cloud.wondrify.asset-pipeline' +} + +extensions.configure(GrailsExtension) { + // Plugins should avoid the spring dependency management plugin due to how it prefers certain libraries + it.springDependencyManagement = false +} + +extensions.configure(AssetPipelineExtension) { + it.packagePlugin = true +} diff --git a/build-logic/src/main/groovy/config.publish-root.gradle b/build-logic/src/main/groovy/config.publish-root.gradle new file mode 100644 index 0000000..49be5d9 --- /dev/null +++ b/build-logic/src/main/groovy/config.publish-root.gradle @@ -0,0 +1,16 @@ +// Workaround needed for nexus publishing bug +// version and group must be specified in the root project +// https://github.com/gradle-nexus/publish-plugin/issues/310 +version = projectVersion +group = 'io.github.gpc' + +def publishedProjects = projectsToPublish + .tokenize(',') + .collect { it.trim() } + .findAll() + +subprojects { + if (name in publishedProjects) { + apply(plugin: 'org.apache.grails.gradle.grails-publish') + } +} diff --git a/build-logic/src/main/groovy/config.publish.gradle b/build-logic/src/main/groovy/config.publish.gradle new file mode 100644 index 0000000..cf0e078 --- /dev/null +++ b/build-logic/src/main/groovy/config.publish.gradle @@ -0,0 +1,9 @@ +// Useful when testing a release version locally and not wanting to setup signing +pluginManager.withPlugin('signing') { + if (System.getenv('DISABLE_BUILD_SIGNING')) { + logger.lifecycle('Signing is disabled for this build per configuration.') + tasks.withType(Sign).configureEach { + enabled = false + } + } +} diff --git a/build-logic/src/main/groovy/config.testing.gradle b/build-logic/src/main/groovy/config.testing.gradle new file mode 100644 index 0000000..667d35b --- /dev/null +++ b/build-logic/src/main/groovy/config.testing.gradle @@ -0,0 +1,45 @@ +import com.adarshr.gradle.testlogger.TestLoggerExtension + +plugins { + id 'com.adarshr.test-logger' +} + +def isCi = System.getenv('CI') != null +def isWindows = System.getProperty('os.name')?.toLowerCase()?.contains('windows') + +// This configures the 'pretty' test logging +// mocha-parallel uses Unicode symbols that require special config on Windows; +// standard-parallel is a safe fallback there. +extensions.configure(TestLoggerExtension) { + it.theme = isCi ? 'plain-parallel' : (isWindows ? 'standard-parallel' : 'mocha-parallel') + it.showExceptions = true + it.showStandardStreams = false + it.showSummary = true + it.showPassed = true + it.showSkipped = true + it.showFailed = true +} + +tasks.withType(Test).configureEach { + onlyIf { + !project.hasProperty('skipTests') + } + + useJUnitPlatform() + + maxHeapSize = '1g' // set to match the groovy compile task to ensure the worker daemons are reused + + reports { + junitXml.required = false + html.required = true + } + + testLogging { + showStandardStreams = false + exceptionFormat = 'full' + stackTraceFilters = ['groovy'] + events = ['failed', 'skipped', 'standardError'] + showStackTraces = true + showCauses = true + } +} diff --git a/build.gradle b/build.gradle index 402930d..f4e064b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,188 +1,7 @@ -buildscript { - repositories { - mavenLocal() - maven { url "https://repo.grails.org/grails/core" } - } - dependencies { - classpath "org.grails:grails-gradle-plugin:$grailsGradlePluginVersion" - classpath "org.grails.plugins:hibernate5:$gormHibernate5Version" - classpath "io.github.gradle-nexus:publish-plugin:1.0.0" - } +plugins { + id 'idea' + id 'config.docs' + id 'config.publish-root' } -group "io.github.gpc" - -apply plugin:"org.grails.grails-plugin" -apply plugin: "maven-publish" -apply plugin: "signing" -apply plugin: "io.github.gradle-nexus.publish-plugin" - -repositories { - mavenLocal() - maven { url "https://repo.grails.org/grails/core" } -} - -dependencies { - implementation "org.grails:grails-core" - - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-starter-actuator" - implementation "org.springframework.boot:spring-boot-starter-validation" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.springframework.boot:spring-boot-starter-tomcat" - - implementation "org.grails:grails-web-boot" - implementation "org.grails.plugins:hibernate5" - implementation "org.hibernate:hibernate-core" - implementation "org.grails:grails-logging" - implementation "org.grails:grails-plugin-rest" - implementation "org.grails:grails-plugin-databinding" - implementation "org.grails:grails-plugin-i18n" - implementation "org.grails:grails-plugin-services" - implementation "org.grails:grails-plugin-url-mappings" - implementation "org.grails:grails-plugin-interceptors" - implementation "org.grails:grails-datastore-gorm-hibernate5:$gormHibernate5Version" - implementation "org.grails.plugins:cache" - implementation "org.grails.plugins:async" - profile "org.grails.profiles:web-plugin" - runtimeOnly "com.h2database:h2" - runtimeOnly "org.apache.tomcat:tomcat-jdbc" - compileOnly "io.micronaut:micronaut-inject-groovy" - testImplementation "io.micronaut:micronaut-inject-groovy" - testImplementation "org.grails:grails-gorm-testing-support" - testImplementation "org.grails:grails-web-testing-support" - - console "org.grails:grails-console" -} - -jar { - exclude "grails/plugins/taggable/Test*" -} - -publishing { - publications { - maven(MavenPublication) { - groupId = project.group - artifactId = 'grails-taggable-plugin' - version = project.version - - from components.java - artifact sourcesJar - artifact javadocJar - - pom { - name = 'Taggable Grails Plugin' - description = 'The Taggable that adds a generic mechanism for tagging data.' - url = 'https://github.com/gpc/taggable' - licenses { - license { - name = 'The Apache License, Version 2.0' - url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id = 'graemerocher' - name = 'Graeme Rocher' - } - developer { - id = 'marcpalmer' - name = 'Marc Palmer' - } - developer { - id = 'pledbrook' - name = 'Peter Ledbrook' - } - developer { - id = 'rhyolight' - name = 'Matthew Taylor ' - } - developer { - id = 'lhotari' - name = 'Lari Hotari' - } - developer { - id = 'codeconsole' - name = 'Scott Murphy' - } - developer { - id = 'jjelliott' - name = 'JJ' - } - developer { - id = 'cazacugmihai' - name = 'Mihai Cazacu' - } - developer { - id = 'stokito' - name = 'Sergey Ponomarev' - } - developer { - id = 'jeffbrown' - name = 'Jeff Scott Brown' - } - developer { - id = 'sbglasius' - name = 'Søren Berg Glasius' - } - } - scm { - connection = 'scm:git:git://github.com/gpc/taggable.git' - developerConnection = 'scm:git:ssh://github.com:gpc/taggable.git' - url = 'https://github.com/gpc/taggable' - } - } - } - } -} - -ext."signing.keyId" = project.findProperty('signing.keyId') ?: System.getenv('SIGNING_KEY_ID') -ext."signing.password" = project.findProperty('signing.password') ?: System.getenv('SIGNING_PASSPHRASE') -ext."signing.secretKeyRingFile" = project.findProperty('signing.secretKeyRingFile') ?: (System.getenv('SIGNING_PASSPHRASE') ?: "${System.getProperty('user.home')}/.gnupg/secring.gpg") - -ext.isReleaseVersion = !version.endsWith("SNAPSHOT") - -afterEvaluate { - signing { - required { isReleaseVersion } - sign publishing.publications.maven - } -} - -tasks.withType(Sign) { - onlyIf { isReleaseVersion } -} - -nexusPublishing { - repositories { - sonatype { - def ossUser = System.getenv("SONATYPE_USERNAME") ?: project.findProperty('sonatypeOss2Username') ?: '' - def ossPass = System.getenv("SONATYPE_PASSWORD") ?: project.findProperty("sonatypeOss2Password") ?: '' - def ossStagingProfileId = System.getenv("SONATYPE_STAGING_PROFILE_ID") ?: project.findProperty("sonatypeOssStagingProfileIdJms") ?: '' - - nexusUrl = uri("https://s01.oss.sonatype.org/service/local/") - snapshotRepositoryUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") - username = ossUser - password = ossPass - stagingProfileId = ossStagingProfileId - } - } -} - -task snapshotVersion { - doLast { - if (!project.version.endsWith('-SNAPSHOT')) { - ant.propertyfile(file: "gradle.properties") { - entry(key: "version", value: "${project.version}-SNAPSHOT") - } - } - } -} - -tasks.withType(Test) { - useJUnitPlatform() - testLogging { - events = ["passed", "failed", "skipped"] - showStandardStreams = false - } -} +// Intentionally left blank - use composition instead diff --git a/docs/build.gradle b/docs/build.gradle new file mode 100644 index 0000000..efdbab7 --- /dev/null +++ b/docs/build.gradle @@ -0,0 +1,41 @@ +import org.asciidoctor.gradle.jvm.AsciidoctorTask + +plugins { + id 'org.asciidoctor.jvm.convert' +} + +version = projectVersion +group = 'io.github.gpc' + +def asciidoctorAttributes = [ + 'source-highlighter': 'coderay', + toc : 'left', + toclevels : '2', + 'toc-title' : 'Table of Contents', + icons : 'font', + id : "${project.name - '-docs'}:${project.version}", + idprefix : '', + idseparator : '-', + version : project.version, + projectUrl : "https://github.com/${rootProject.findProperty('githubUser')}/${rootProject.findProperty('githubProject')}", + sourcedir : "${rootProject.allprojects.find { it.name == 'taggable' }.projectDir}/src/main/groovy", + grailsVersion : grailsVersion +] + +tasks.named('asciidoctor', AsciidoctorTask) { + + outputDir = project.layout.buildDirectory.dir('docs') + sourceDir = project.layout.projectDirectory.dir('src/docs') + + attributes(asciidoctorAttributes) + baseDirFollowsSourceDir() + options(doctype: 'book') + sources { include 'index.adoc' } + + jvm { + jvmArgs( + '--add-opens', 'java.base/java.io=ALL-UNNAMED', + '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED' + ) + } +} diff --git a/docs/src/docs/gettingStarted.adoc b/docs/src/docs/gettingStarted.adoc new file mode 100644 index 0000000..a31a642 --- /dev/null +++ b/docs/src/docs/gettingStarted.adoc @@ -0,0 +1,52 @@ +[[gettingStarted]] +== Getting Started + +=== Requirements + +* Grails {grailsVersion} +* Java 17+ + +=== Installation + +Add the plugin to your application's `build.gradle`: + +[source,groovy,subs="attributes"] +---- +dependencies { + implementation 'io.github.gpc:taggable:{version}' +} +---- + +=== Case sensitivity + +By default, all tags are forced to lower case. Set the following in `application.yml` to preserve the supplied case: + +[source,yaml] +---- +grails: + taggable: + preserve: + case: true +---- + +Finder methods continue to treat each tag as a discrete string, so `"grails"` and `"Grails"` are independent tags when +case preservation is on. + +=== Custom table names + +[source,groovy] +---- +grails.taggable.tag.table = 'MY_TAGS' +grails.taggable.tagLink.table = 'MY_TAG_LINKS' +---- + +=== Hibernate autoImport + +Auto-import is disabled by default for `Tag` / `TagLink` to avoid conflicts with consumer-domain classes of the same +name. Re-enable if required: + +[source,groovy] +---- +grails.taggable.tag.autoImport = true +grails.taggable.tagLink.autoImport = true +---- diff --git a/docs/src/docs/index.adoc b/docs/src/docs/index.adoc new file mode 100644 index 0000000..60f8e2a --- /dev/null +++ b/docs/src/docs/index.adoc @@ -0,0 +1,8 @@ += Taggable Plugin - Reference Documentation +Grails Plugin Collective + +include::introduction.adoc[] + +include::gettingStarted.adoc[] + +include::usage.adoc[] diff --git a/docs/src/docs/index.tmpl b/docs/src/docs/index.tmpl new file mode 100644 index 0000000..2d366d3 --- /dev/null +++ b/docs/src/docs/index.tmpl @@ -0,0 +1,389 @@ + + + + + + Taggable - Grails Plugin + + + + + + +
+ + + + + View on GitHub + +
+

Export

+

A Grails plugin that adds export functionality for CSV, Excel, ODS, PDF, RTF, and XML formats

+
+
+ +
+

Documentation

+ +
+
+ Latest Release +

Stable Version

+ +
+ +
+ Snapshot +

Development Version

+ +
+
+ +

Other Versions

+
+
+ + +
+
+
+ + + + + + diff --git a/docs/src/docs/introduction.adoc b/docs/src/docs/introduction.adoc new file mode 100644 index 0000000..99090bb --- /dev/null +++ b/docs/src/docs/introduction.adoc @@ -0,0 +1,12 @@ +[[introduction]] +== Introduction + +The Taggable plugin adds a generic mechanism for tagging any Grails domain class. It supports method chaining, +polymorphic tag queries across class hierarchies, configurable case sensitivity, and a GSP tag library for rendering +tag clouds. + +include::introduction/sourceCode.adoc[] + +include::introduction/currentVersion.adoc[] + +include::introduction/license.adoc[] diff --git a/docs/src/docs/introduction/currentVersion.adoc b/docs/src/docs/introduction/currentVersion.adoc new file mode 100644 index 0000000..a35dc9d --- /dev/null +++ b/docs/src/docs/introduction/currentVersion.adoc @@ -0,0 +1,14 @@ +[[currentVersion]] +=== Current version + +Current version is *{revnumber}*. + +|=== +| Plugin release | Supported Grails Version +| 7.x | >= 7.0 +| 6.x | 6.x +| 5.x | 5.x +| 4.x | 4.x +|=== + +This version is developed against *Grails {grailsVersion}*. diff --git a/docs/src/docs/introduction/license.adoc b/docs/src/docs/introduction/license.adoc new file mode 100644 index 0000000..6645ac0 --- /dev/null +++ b/docs/src/docs/introduction/license.adoc @@ -0,0 +1,6 @@ +[[license]] +=== License + +This plugin is released under the http://www.apache.org/licenses/LICENSE-2.0[Apache License, Version 2.0]. + +See https://github.com/gpc/taggable/blob/HEAD/LICENSE.txt for the full license text. diff --git a/docs/src/docs/introduction/sourceCode.adoc b/docs/src/docs/introduction/sourceCode.adoc new file mode 100644 index 0000000..eb21e33 --- /dev/null +++ b/docs/src/docs/introduction/sourceCode.adoc @@ -0,0 +1,8 @@ +[[sourceCode]] +=== Source code + +The full source code for this plugin can be found on https://github.com/gpc/taggable[GitHub]. + +For issues, improvements, or new features, go to https://github.com/gpc/taggable/issues[GitHub issues]. + +Feel free to PUSH any correction about this document. diff --git a/docs/src/docs/usage.adoc b/docs/src/docs/usage.adoc new file mode 100644 index 0000000..48451e3 --- /dev/null +++ b/docs/src/docs/usage.adoc @@ -0,0 +1,93 @@ +[[usage]] +== Usage + +=== Making a domain class taggable + +[source,groovy] +---- +import grails.plugins.taggable.* + +class Vehicle implements Taggable { } +---- + +=== Adding, removing, and replacing tags + +[source,groovy] +---- +def v = Vehicle.get(1) + +v.addTag('red') + .addTag('sporty') + .addTag('expensive') + +v.addTags(['electric', 'hybrid']) + +v.setTags(['red', 'sporty', 'expensive']) + +v.removeTag('expensive') + +def tags = v.tags // ['red', 'sporty'] +---- + +=== Querying by tag + +[source,groovy] +---- +def vehicles = Vehicle.findAllByTag('sporty') // also accepts [max: 5] etc. +def count = Vehicle.countByTag('sporty') +def totalTags = Vehicle.totalTags +def allTags = Vehicle.allTags + +def teslaElectricCars = Vehicle.findAllByTagWithCriteria('electric') { + eq('manufacturer', 'Tesla Motors') +} + +def fiveCoolTags = Vehicle.findAllTagsWithCriteria([max: 5]) { + ilike('name', '%cool%') +} +---- + +=== Querying by tag with HQL + +`Tag` and `TagLink` are mapped GORM entities; you can use them in HQL: + +[source,groovy] +---- +def gasGuzzlers = Vehicle.executeQuery(''' + SELECT vehicle + FROM Vehicle vehicle, TagLink tagLink + WHERE vehicle.id = tagLink.tagRef + AND tagLink.type = 'Vehicle' + AND tagLink.tag.name IN (:tags) +''', [tags: ['SUV', 'gas-guzzler', 'tank', 'boat']]) +---- + +=== Parsing delimited tag strings + +[source,groovy] +---- +v.parseTags('red,sporty,expensive') +v.parseTags('red/sporty/expensive', '/') +---- + +=== Tag cloud taglib + +[source,html] +---- + +---- + +Override the default CSS size classes via configuration: + +[source,yaml] +---- +grails: + taggable: + css: + classes: + - tiny + - small + - medium + - large + - huge +---- diff --git a/examples/app1/build.gradle b/examples/app1/build.gradle new file mode 100644 index 0000000..3e6a918 --- /dev/null +++ b/examples/app1/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'config.example-app' +} + +version = projectVersion +group = 'app1' + +dependencies { + implementation project(':taggable') + profile "org.apache.grails.profiles:web" + developmentOnly "org.springframework.boot:spring-boot-devtools" + // Spring Boot DevTools may cause performance slowdowns or compatibility issues on larger applications + testAndDevelopmentOnly "org.webjars.npm:bootstrap" + testAndDevelopmentOnly "org.webjars.npm:bootstrap-icons" + testAndDevelopmentOnly "org.webjars.npm:jquery" + implementation platform("org.apache.grails:grails-bom:$grailsVersion") + implementation "org.apache.grails:grails-core" + implementation "org.apache.grails:grails-data-hibernate5" + implementation "org.apache.grails:grails-databinding" + implementation "org.apache.grails:grails-events" + implementation "org.apache.grails:grails-gsp" + implementation "org.apache.grails:grails-interceptors" + implementation "org.apache.grails:grails-layout" + implementation "org.apache.grails:grails-logging" + implementation "org.apache.grails:grails-rest-transforms" + implementation "org.apache.grails:grails-scaffolding" + implementation "org.apache.grails:grails-services" + implementation "org.apache.grails:grails-url-mappings" + implementation "org.apache.grails:grails-web-boot" + implementation "org.springframework.boot:spring-boot-autoconfigure" + implementation "org.springframework.boot:spring-boot-starter" + implementation "org.springframework.boot:spring-boot-starter-actuator" + implementation "org.springframework.boot:spring-boot-starter-logging" + implementation "org.springframework.boot:spring-boot-starter-tomcat" + implementation "org.springframework.boot:spring-boot-starter-validation" + console "org.apache.grails:grails-console" + runtimeOnly "cloud.wondrify:asset-pipeline-grails" + runtimeOnly "com.h2database:h2" + runtimeOnly "com.zaxxer:HikariCP" + runtimeOnly "org.fusesource.jansi:jansi" + integrationTestImplementation testFixtures("org.apache.grails:grails-geb") + testImplementation "org.apache.grails:grails-testing-support-datamapping" + testImplementation "org.apache.grails:grails-testing-support-web" + testImplementation "org.mockito:mockito-core" + testImplementation "org.spockframework:spock-core" +} + diff --git a/examples/app1/grails-app/assets/stylesheets/application.css b/examples/app1/grails-app/assets/stylesheets/application.css new file mode 100644 index 0000000..c1b6dbb --- /dev/null +++ b/examples/app1/grails-app/assets/stylesheets/application.css @@ -0,0 +1,5 @@ +/* + * This is a manifest file that'll be compiled into application.css. + * + *= require_self + */ diff --git a/examples/app1/grails-app/conf/application.yml b/examples/app1/grails-app/conf/application.yml new file mode 100644 index 0000000..86ab46f --- /dev/null +++ b/examples/app1/grails-app/conf/application.yml @@ -0,0 +1,93 @@ +--- +grails: + profile: web + codegen: + defaultPackage: grails.plugins.taggable +info: + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' +spring: + groovy: + template: + check-template-location: false +server: + port: 8081 + +--- +grails: + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + urlmapping: + cache: + maxsize: 1000 + controllers: + defaultScope: singleton + converters: + encoding: UTF-8 + views: + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlets: html + taglib: none + staticparts: none +--- +dataSource: + driverClassName: org.h2.Driver + username: sa + password: '' + pooled: true + jmxExport: true +environments: + development: + dataSource: + dbCreate: create-drop + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + test: + dataSource: + dbCreate: create-drop + url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + production: + dataSource: + dbCreate: none + url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE +hibernate: + cache: + queries: false + use_second_level_cache: false + use_query_cache: false diff --git a/grails-app/conf/logback.xml b/examples/app1/grails-app/conf/logback.xml similarity index 100% rename from grails-app/conf/logback.xml rename to examples/app1/grails-app/conf/logback.xml diff --git a/grails-app/domain/grails/plugins/taggable/TestDescendent.groovy b/examples/app1/grails-app/domain/grails/plugins/taggable/TestDescendent.groovy similarity index 100% rename from grails-app/domain/grails/plugins/taggable/TestDescendent.groovy rename to examples/app1/grails-app/domain/grails/plugins/taggable/TestDescendent.groovy diff --git a/grails-app/domain/grails/plugins/taggable/TestDomain.groovy b/examples/app1/grails-app/domain/grails/plugins/taggable/TestDomain.groovy similarity index 100% rename from grails-app/domain/grails/plugins/taggable/TestDomain.groovy rename to examples/app1/grails-app/domain/grails/plugins/taggable/TestDomain.groovy diff --git a/grails-app/init/grails/plugins/taggable/Application.groovy b/examples/app1/grails-app/init/grails/plugins/taggable/Application.groovy similarity index 80% rename from grails-app/init/grails/plugins/taggable/Application.groovy rename to examples/app1/grails-app/init/grails/plugins/taggable/Application.groovy index 09408b0..435540a 100644 --- a/grails-app/init/grails/plugins/taggable/Application.groovy +++ b/examples/app1/grails-app/init/grails/plugins/taggable/Application.groovy @@ -1,13 +1,10 @@ package grails.plugins.taggable - import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration -import grails.plugins.metadata.PluginSource -@PluginSource class Application extends GrailsAutoConfiguration { static void main(String[] args) { GrailsApp.run(Application, args) } -} \ No newline at end of file +} diff --git a/examples/app1/grails-app/views/layouts/main.gsp b/examples/app1/grails-app/views/layouts/main.gsp new file mode 100644 index 0000000..137125a --- /dev/null +++ b/examples/app1/grails-app/views/layouts/main.gsp @@ -0,0 +1,13 @@ + + + + + + <g:layoutTitle default="Taggable"/> + + + + + + + diff --git a/examples/app1/src/integration-test/groovy/grails/plugins/taggable/TaggableSpec.groovy b/examples/app1/src/integration-test/groovy/grails/plugins/taggable/TaggableSpec.groovy new file mode 100644 index 0000000..4fa065a --- /dev/null +++ b/examples/app1/src/integration-test/groovy/grails/plugins/taggable/TaggableSpec.groovy @@ -0,0 +1,283 @@ +package grails.plugins.taggable + +import spock.lang.Specification + +import grails.gorm.transactions.Rollback +import grails.testing.mixin.integration.Integration + +@Integration +@Rollback +class TaggableSpec extends Specification { + + void setup() { + Tag.preserveCaseForTesting = false + } + + void testAddTagMethodCaseInsensitive() { + given: + def td = new TestDomain(name: "foo") + td.save() + + when: + td.addTag("Groovy") + .addTag("grails") + and: + def links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 2 + links.tag.name == ['groovy', 'grails'] + } + + void testAddTagMethodCasePreserving() { + given: + Tag.preserveCaseForTesting = true + + def td = new TestDomain(name: "foo") + td.save() + + when: + td.addTag("Groovy") + .addTag("grails") + + def links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + then: + links.size() == 2 + links.tag.name == ['Groovy', 'grails'] + + when: + // adding a second, even if preserving case in DB it should still not add it as already has such a tag + td.addTag("groovy") + and: + links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 3 + links.tag.name == ['Groovy', 'grails', 'groovy'] + } + + void testAddTagsMethod() { + given: + def td = new TestDomain(name: "foo") + td.save() + + when: + td.addTags(["groovy", "grails"]) + and: + def links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 2 + links.tag.name == ['groovy', 'grails'] + } + + void testRemoveTagMethod() { + given: + def td = new TestDomain(name: "foo") + td.save() + + when: + td.addTag("groovy") + .addTag("grails") + and: + def links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 2 + links.tag.name == ['groovy', 'grails'] + + when: + td.removeTag("groovy") + and: + links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 1 + links.tag.name == ['grails'] + } + + void testGetTagsMethod() { + given: + def td = new TestDomain(name: "foo") + td.save() + + when: + td.addTag("groovy") + .addTag("grails") + td.save(flush: true) + + and: + TestDomain.withSession { session -> session.clear() } + td = TestDomain.findByName("foo") + + then: + td.tags == ['groovy', 'grails'] + } + + void testSetTagsMethod() { + given: + def td = new TestDomain(name: "foo") + td.save() + + when: + td.tags = ["groovy", null, "grails", ''] + and: + def links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 2 + links.tag.name == ['groovy', 'grails'] + td.tags == ['groovy', 'grails'] + + when: + td.tags = ["foo", "bar"] + and: + links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 2 + links.tag.name.sort(true) == ['foo', 'bar'].sort(true) + td.tags.sort(true) == ['foo', 'bar'].sort(true) + + when: + td.tags = [] + and: + links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') + + then: + links.size() == 0 + links.tag.name == [] + td.tags == [] + } + + void testFindAllByTag() { + given: + new TestDomain(name: "foo") + .save() + .addTag("groovy") + .addTag("grails") + .addTag("griffon") + new TestDomain(name: "bar") + .save() + .addTag("groovy") + .addTag("grails") + + when: + def results = TestDomain.findAllByTag("groovy") + + then: + results.size() == 2 + results[0] instanceof TestDomain + TestDomain.findAllByTag("groovy").size() == 2 + TestDomain.findAllByTag("grails").size() == 2 + TestDomain.findAllByTag("griffon").size() == 1 + TestDomain.findAllByTag("nothing").size() == 0 + TestDomain.findAllByTag(null).size() == 0 + } + + void testFindAllByTagPolymorphic() { + given: + new TestDomain(name: "foo") + .save() + .addTag("groovy") + .addTag("grails") + .addTag("griffon") + new TestDescendent(name: "bar", other: 'bla') + .save() + .addTag("groovy") + .addTag("grails") + .addTag("gradle") + + when: + def results = TestDomain.findAllByTag("groovy") + + then: + results.size() == 2 + results[0] instanceof TestDomain + TestDomain.findAllByTag("groovy").size() == 2 + TestDescendent.findAllByTag("groovy").size() == 1 + TestDomain.findAllByTag("grails").size() == 2 + TestDescendent.findAllByTag("grails").size() == 1 + TestDomain.findAllByTag("gradle").size() == 1 + TestDescendent.findAllByTag("gradle").size() == 1 + TestDomain.findAllByTag("griffon").size() == 1 + TestDomain.findAllByTag("nothing").size() == 0 + TestDomain.findAllByTag(null).size() == 0 + } + + void testCountByTag() { + given: + new TestDomain(name: "foo") + .save() + .addTag("groovy") + .addTag("grails") + .addTag("griffon") + new TestDomain(name: "bar") + .save() + .addTag("groovy") + .addTag("grails") + new TestDescendent(name: "bla", other: 'zzzz') + .save() + .addTag("groovy") + .addTag("grails") + .addTag("gradle") + + expect: + TestDomain.countByTag("groovy") == 3 + TestDescendent.countByTag("groovy") == 1 + TestDomain.countByTag("griffon") == 1 + TestDescendent.countByTag("griffon") == 0 + TestDomain.countByTag("gradle") == 1 + TestDescendent.countByTag("gradle") == 1 + TestDomain.countByTag("rubbish") == 0 + TestDomain.countByTag(null) == 0 + } + + void testAllTags() { + given: + new TestDomain(name: "foo") + .save() + .addTag("groovy") + .addTag("grails") + .addTag("griffon") + new TestDomain(name: "bar") + .save() + .addTag("groovy") + .addTag("grails") + new TestDescendent(name: "bla", other: 'zzzz') + .save() + .addTag("groovy") + .addTag("grails") + .addTag("gradle") + + expect: + TestDomain.allTags.sort(true) == ['gradle', 'grails', 'griffon', 'groovy'].sort(true) + TestDomain.totalTags == 4 + TestDescendent.allTags.sort(true) == ['gradle', 'grails', 'groovy'].sort(true) + TestDescendent.totalTags == 3 + } + + void testParseTags() { + given: + def td = new TestDomain(name: "foo") + .save() + + when: + td.parseTags("groovy,grails,griffon") + + then: + TestDomain.allTags == ['grails', 'griffon', 'groovy'] + } + + void testParseTagsWithDelimiter() { + given: + def td = new TestDomain(name: "foo") + .save() + + when: + td.parseTags("groovy grails griffon", " ") + + then: + TestDomain.allTags == ['grails', 'griffon', 'groovy'] + } +} diff --git a/gradle.properties b/gradle.properties index 30982f7..371a5a2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,18 @@ -version=6.0.0-SNAPSHOT -grailsVersion=6.1.0 -gorm.version=8.0.2 -gormHibernate5Version=8.0.1 -grailsGradlePluginVersion=6.1.0 -groovyVersion=3.0.11 +projectVersion=7.0.0-SNAPSHOT +grailsVersion=7.0.10 +githubUser=gpc +githubProject=taggable +# Comma-separated list of projects to publish +projectsToPublish=taggable +# Build dependencies +asciidoctorVersion=4.0.5 +testLoggerVersion=4.0.0 +# Enable and set agree=yes to publish build scans from GitHub workflows +ciBuildScanPublish=true +ciBuildScanTermsOfUseUrl=https://gradle.com/terms-of-service +ciBuildScanTermsOfUseAgree=yes + +org.gradle.caching=true org.gradle.daemon=true org.gradle.parallel=true -org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M \ No newline at end of file +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index deedc7f..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d2d5d93..aaaabb3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip diff --git a/gradlew b/gradlew index 9d82f78..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -1,74 +1,129 @@ -#!/usr/bin/env bash +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null +CLASSPATH="\\\"\\\"" -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -77,84 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec9973..5eed7ee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -8,26 +26,30 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,54 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml deleted file mode 100644 index 61b5bf3..0000000 --- a/grails-app/conf/application.yml +++ /dev/null @@ -1,110 +0,0 @@ ---- -grails: - profile: web-plugin - codegen: - defaultPackage: grails.plugins.taggable -info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - groovy: - template: - check-template-location: false - ---- -grails: - mime: - disable: - accept: - header: - userAgents: - - Gecko - - WebKit - - Presto - - Trident - types: - all: '*/*' - atom: application/atom+xml - css: text/css - csv: text/csv - form: application/x-www-form-urlencoded - html: - - text/html - - application/xhtml+xml - js: text/javascript - json: - - application/json - - text/json - multipartForm: multipart/form-data - rss: application/rss+xml - text: text/plain - hal: - - application/hal+json - - application/hal+xml - xml: - - text/xml - - application/xml - urlmapping: - cache: - maxsize: 1000 - controllers: - defaultScope: singleton - converters: - encoding: UTF-8 - views: - default: - codec: html - gsp: - encoding: UTF-8 - htmlcodec: xml - codecs: - expression: html - scriptlets: html - taglib: none - staticparts: none ---- -hibernate: - cache: - queries: false - use_second_level_cache: false - use_query_cache: false -dataSource: - pooled: true - jmxExport: true - driverClassName: org.h2.Driver - username: sa - password: - -environments: - development: - dataSource: - dbCreate: create-drop - url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - test: - dataSource: - dbCreate: update - url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - production: - dataSource: - dbCreate: update - url: jdbc:h2:prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - properties: - jmxEnabled: true - initialSize: 5 - maxActive: 50 - minIdle: 5 - maxIdle: 25 - maxWait: 10000 - maxAge: 600000 - timeBetweenEvictionRunsMillis: 5000 - minEvictableIdleTimeMillis: 60000 - validationQuery: SELECT 1 - validationQueryTimeout: 3 - validationInterval: 15000 - testOnBorrow: true - testWhileIdle: true - testOnReturn: false - jdbcInterceptors: ConnectionState - defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED diff --git a/plugin/build.gradle b/plugin/build.gradle new file mode 100644 index 0000000..e8cdee9 --- /dev/null +++ b/plugin/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'config.compile' + id 'config.grails-plugin' + id 'config.publish' + id 'config.testing' +} + +version = projectVersion +group = 'io.github.gpc' + +dependencies { + profile 'org.apache.grails.profiles:web-plugin' + console 'org.apache.grails:grails-console' + + compileOnly platform("org.apache.grails:grails-bom:$grailsVersion") + compileOnly 'org.apache.grails:grails-dependencies-starter-web' + compileOnly 'org.apache.grails.data:grails-datastore-core' + + testImplementation platform("org.apache.grails:grails-bom:$grailsVersion") + testImplementation 'org.apache.grails:grails-dependencies-starter-web' + testImplementation 'org.apache.grails:grails-dependencies-test' + testImplementation 'org.apache.grails:grails-testing-support-web' +} + +extensions.configure(org.apache.grails.gradle.publish.GrailsPublishExtension) { + def githubUser = rootProject.findProperty('githubUser') + def githubProject = rootProject.findProperty('githubProject') + it.artifactId = project.name + it.githubSlug = "${githubUser}/${githubProject}" + it.license.name = 'Apache-2.0' + it.title = 'Grails Taggable Plugin' + it.desc = 'Adds a generic mechanism for tagging any Grails domain class, with tag-cloud taglib support.' + it.organization { + it.name = 'Grails Plugin Collective (GPC)' + it.url = "https://github.com/${githubUser}" + } + it.developers = [ + graemerocher : 'Graeme Rocher', + pledbrook : 'Peter Ledbrook', + marcpalmer : 'Marc Palmer', + jjelliott : 'jjelliott', + codeconsole : 'Scott Murphy', + sbglasius : 'Søren Berg Glasius', + lhotari : 'Lari Hotari', + rhyolight : 'Matthew Taylor', + eusorov : 'Evgeny Usorov', + cazacugmihai : 'Mihai Cazacu', + jeffscottbrown: 'Jeff Scott Brown', + stokito : 'Sergey Ponomarev', + ] +} diff --git a/plugin/grails-app/assets/images/.gitkeep b/plugin/grails-app/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugin/grails-app/conf/application.yml b/plugin/grails-app/conf/application.yml new file mode 100644 index 0000000..f9adc84 --- /dev/null +++ b/plugin/grails-app/conf/application.yml @@ -0,0 +1,10 @@ +--- +grails: + profile: web-plugin + codegen: + defaultPackage: grails.plugins.taggable +info: + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' diff --git a/grails-app/domain/grails/plugins/taggable/Tag.groovy b/plugin/grails-app/domain/grails/plugins/taggable/Tag.groovy similarity index 100% rename from grails-app/domain/grails/plugins/taggable/Tag.groovy rename to plugin/grails-app/domain/grails/plugins/taggable/Tag.groovy diff --git a/grails-app/domain/grails/plugins/taggable/TagLink.groovy b/plugin/grails-app/domain/grails/plugins/taggable/TagLink.groovy similarity index 100% rename from grails-app/domain/grails/plugins/taggable/TagLink.groovy rename to plugin/grails-app/domain/grails/plugins/taggable/TagLink.groovy diff --git a/grails-app/services/grails/plugins/taggable/TaggableService.groovy b/plugin/grails-app/services/grails/plugins/taggable/TaggableService.groovy similarity index 51% rename from grails-app/services/grails/plugins/taggable/TaggableService.groovy rename to plugin/grails-app/services/grails/plugins/taggable/TaggableService.groovy index dbedd7a..52f024e 100644 --- a/grails-app/services/grails/plugins/taggable/TaggableService.groovy +++ b/plugin/grails-app/services/grails/plugins/taggable/TaggableService.groovy @@ -1,7 +1,6 @@ package grails.plugins.taggable import grails.core.GrailsApplication -import grails.core.GrailsDomainClass import grails.util.GrailsNameUtils import org.grails.datastore.mapping.model.MappingContext import org.grails.datastore.mapping.model.PersistentEntity @@ -13,7 +12,7 @@ class TaggableService { GrailsApplication grailsApplication @Autowired - @Qualifier("grailsDomainClassMappingContext") + @Qualifier('grailsDomainClassMappingContext') MappingContext mappingContext def domainClassFamilies = [:] @@ -33,30 +32,21 @@ class TaggableService { } return tagCounts } - + /** - * Update the graph of known subclasses - * - * Example: - * [ - * WcmContent: [ - * WcmBlog, - * WcmHTMLContent, - * WcmComment - * ] - * WcmBlog: [], - * WcmHTMLContent: [WcmRichContent], - * WcmRichContent: [], - * WcmStatus: [] - * ] + * Update the graph of known subclasses for each Taggable domain type. */ def refreshDomainClasses() { - grailsApplication.domainClasses.each {GrailsDomainClass artefact -> - PersistentEntity persistentEntity = mappingContext.getPersistentEntity(artefact.clazz.name) - if (Taggable.class.isAssignableFrom(artefact.clazz)) { - domainClassFamilies[artefact.clazz.name] = [GrailsNameUtils.getPropertyName(artefact.clazz)] - // Add class and all subclasses - domainClassFamilies[artefact.clazz.name].addAll(mappingContext.getChildEntities(persistentEntity).collect {GrailsNameUtils.getPropertyName(it.javaClass)}) + mappingContext.persistentEntities.each { PersistentEntity persistentEntity -> + Class clazz = persistentEntity.javaClass + if (Taggable.isAssignableFrom(clazz)) { + def family = [GrailsNameUtils.getPropertyName(clazz)] + family.addAll( + mappingContext.getChildEntities(persistentEntity).collect { + GrailsNameUtils.getPropertyName(it.javaClass) + } + ) + domainClassFamilies[clazz.name] = family } } } diff --git a/grails-app/taglib/grails/plugins/taggable/TagsTagLib.groovy b/plugin/grails-app/taglib/grails/plugins/taggable/TagsTagLib.groovy similarity index 100% rename from grails-app/taglib/grails/plugins/taggable/TagsTagLib.groovy rename to plugin/grails-app/taglib/grails/plugins/taggable/TagsTagLib.groovy diff --git a/src/main/groovy/grails/plugins/taggable/TagException.groovy b/plugin/src/main/groovy/grails/plugins/taggable/TagException.groovy similarity index 100% rename from src/main/groovy/grails/plugins/taggable/TagException.groovy rename to plugin/src/main/groovy/grails/plugins/taggable/TagException.groovy diff --git a/src/main/groovy/grails/plugins/taggable/Taggable.groovy b/plugin/src/main/groovy/grails/plugins/taggable/Taggable.groovy similarity index 54% rename from src/main/groovy/grails/plugins/taggable/Taggable.groovy rename to plugin/src/main/groovy/grails/plugins/taggable/Taggable.groovy index c4efd6b..6406369 100644 --- a/src/main/groovy/grails/plugins/taggable/Taggable.groovy +++ b/plugin/src/main/groovy/grails/plugins/taggable/Taggable.groovy @@ -14,22 +14,26 @@ */ package grails.plugins.taggable -import grails.util.* + +import grails.util.GrailsNameUtils +import grails.util.Holders +import org.grails.datastore.mapping.query.api.BuildableCriteria + /** * Marker interface to add tagging capabilities to a particular domain instance * @author Graeme Rocher */ trait Taggable { - Taggable addTag(String name) { - if(this.id == null) throw new TagException("You need to save the domain instance before tagging it") + Taggable addTag(String name) { + if (this.id == null) throw new TagException("You need to save the domain instance before tagging it") def tag if (!Tag.preserveCase) { name = name.toLowerCase() } - tag = Tag.findByName(name, [cache:true]) ?: new Tag(name:name).save() - if(!tag) throw new TagException("Value [$name] is not a valid tag") - + tag = Tag.findByName(name, [cache: true]) ?: new Tag(name: name).save() + if (!tag) throw new TagException("Value [$name] is not a valid tag") + def criteria = TagLink.createCriteria() def instance = this def link = criteria.get { @@ -38,44 +42,44 @@ trait Taggable { criteria.eq 'type', GrailsNameUtils.getPropertyName(instance.class) criteria.cache true } - - if(!link) { - link = new TagLink(tag:tag, tagRef:this.id, type:GrailsNameUtils.getPropertyName(this.class)).save() + + if (!link) { + link = new TagLink(tag: tag, tagRef: this.id, type: GrailsNameUtils.getPropertyName(this.class)).save() } return this // for method chaining - } + } - - Taggable addTags( names ) { + + Taggable addTags(names) { names.each { addTag it.toString() } return this } - + Collection tags() { - getTags() + getTags() } def getTags() { - this.id ? getTagLinks(Holders.applicationContext.taggableService, this).tag.name : [] - } + this.id ? getTagLinks(this).tag.name : [] + } - Taggable parseTags( String tags, String delimiter = "," ) { - tags.split(delimiter).each { + Taggable parseTags(String tags, String delimiter = ",") { + tags.split(delimiter).each { def tag = it.trim() - if(tag) addTag(tag) + if (tag) addTag(tag) } return this } - Taggable removeTag( String name ) { - if(this.id == null) throw new TagException("You need to save the domain instance before tagging it") - + Taggable removeTag(String name) { + if (this.id == null) throw new TagException("You need to save the domain instance before tagging it") + if (!Tag.preserveCase) { name = name.toLowerCase() } - + def criteria = TagLink.createCriteria() - def instance =this + def instance = this def link = criteria.get { criteria.tag { if (!Tag.preserveCase) { @@ -87,42 +91,40 @@ trait Taggable { criteria.eq 'tagRef', instance.id criteria.eq 'type', GrailsNameUtils.getPropertyName(instance.class) criteria.cache true - } - link?.delete(flush:true) - return this + } + link?.delete(flush: true) + return this } - Taggable setTags( List tags ) { + Taggable setTags(List tags) { // remove invalid tags - tags = tags?.findAll { it } + tags = tags?.findAll { it } if (tags) { // remove old tags that not appear in the new tags - getTagLinks(Holders.applicationContext.taggableService, this)*.each { TagLink tagLink -> + getTagLinks(this)*.each { TagLink tagLink -> if (tags.contains(tagLink.tag.name)) { tags.remove(tagLink.tag.name) } else { - tagLink.delete(flush:true) // Grails >=3.3.0 requires flush + tagLink.delete(flush: true) // Grails >=3.3.0 requires flush } } // add the rest addTags(tags) } else { - getTagLinks(Holders.applicationContext.taggableService, this)*.delete(flush:true) + getTagLinks(this)*.delete(flush: true) } return this } - - static List getAllTags() { - def criteria = TagLink.createCriteria() + def criteria = TagLink.createCriteria() criteria.list { criteria.projections { criteria.tag { criteria.distinct "name" } } - criteria.'in'('type', (Collection)Holders.applicationContext.taggableService.domainClassFamilies[this.name]) + criteria.'in'('type', (Collection) Holders.applicationContext.getBean(TaggableService).domainClassFamilies[this.name]) criteria.cache true - } + } as List } static Integer getTotalTags() { @@ -130,13 +132,14 @@ trait Taggable { def criteria = TagLink.createCriteria() criteria.get { criteria.projections { criteria.tag { criteria.countDistinct "name" } } - criteria.'in'('type', (Collection)Holders.applicationContext.taggableService.domainClassFamilies[clazz.name]) + criteria.'in'('type', (Collection) Holders.applicationContext.getBean(TaggableService).domainClassFamilies[clazz.name]) criteria.cache true - } + } as Integer } - static Integer countByTag( String tag ) { - def identifiers = getTagReferences(Holders.applicationContext.taggableService, tag, this.name) - if(identifiers) { + + static Integer countByTag(String tag) { + def identifiers = getTagReferences(tag, this.name) + if (identifiers) { def criteria = createCriteria() criteria.get { criteria.projections { @@ -145,56 +148,53 @@ trait Taggable { criteria.inList 'id', identifiers criteria.cache true } - } - else { - return 0 + } else { + return 0 } } - - static List findAllByTag( String name ) { - def identifiers = getTagReferences(Holders.applicationContext.taggableService, name, this.name) - if(identifiers) { - return findAllByIdInList(identifiers, [cache:true]) - } - else { - return Collections.EMPTY_LIST + + static List findAllByTag(String name) { + def identifiers = getTagReferences(name, this.name) + if (identifiers) { + return findAllByIdInList(identifiers, [cache: true]) + } else { + return Collections.EMPTY_LIST } } - static List findAllByTag ( String name, Map args ) { - def identifiers = getTagReferences(Holders.applicationContext.taggableService, name, this.name) - if(identifiers) { - args.cache=true + static List findAllByTag(String name, Map args) { + def identifiers = getTagReferences(name, this.name) + if (identifiers) { + args.cache = true return findAllByIdInList(identifiers, args) - } - else { - return Collections.EMPTY_LIST + } else { + return Collections.EMPTY_LIST } } - static List findAllByTagWithCriteria( String name, Closure crit ) { + static List findAllByTagWithCriteria(String name, Closure crit) { def clazz = this - def identifiers = getTagReferences(Holders.applicationContext.taggableService, name, clazz.name) - if(identifiers) { + def identifiers = getTagReferences(name, clazz.name) + if (identifiers) { return clazz.withCriteria { 'in'('id', identifiers) crit.delegate = delegate crit.call() } - } - else { + } else { return Collections.EMPTY_LIST } } - static List findAllTagsWithCriteria( Map params, Closure crit ) { + + static List findAllTagsWithCriteria(Map params, Closure crit) { def clazz = this - def criteria = TagLink.createCriteria() + BuildableCriteria criteria = TagLink.createCriteria() criteria.list { - criteria.projections { criteria.tag { criteria.distinct "name" } } - criteria.'in'('type', Holders.applicationContext.taggableService.domainClassFamilies[clazz.name]) - criteria.cache true - criteria.tag(crit) + criteria.projections { criteria.tag { criteria.distinct "name" } } + criteria.'in'('type', Holders.applicationContext.getBean(TaggableService).domainClassFamilies[clazz.name]) + criteria.cache true + criteria.tag(crit) if (params.offset != null) { criteria.firstResult(params.offset.toInteger()) @@ -205,31 +205,33 @@ trait Taggable { criteria.tag { criteria.order('name', 'asc') } - } + } as List } - private getTagLinks(tagService, obj) { - TagLink.findAllByTagRefAndTypeInList(obj.id, tagService.domainClassFamilies[obj.class.name], [cache:true]) + + private getTagLinks(obj) { + TaggableService tagService = Holders.applicationContext.getBean(TaggableService) + TagLink.findAllByTagRefAndTypeInList(obj.id, tagService.domainClassFamilies[obj.class.name], [cache: true]) } - private static getTagReferences(tagService, String tagName, String className) { - if(tagName) { - def criteria = TagLink.createCriteria() + private static getTagReferences(String tagName, String className) { + TaggableService tagService = Holders.applicationContext.getBean(TaggableService) + if (tagName) { + def criteria = TagLink.createCriteria() criteria.list { criteria.projections { criteria.property 'tagRef' } criteria.tag { - criteria.eq 'name', tagName - } + criteria.eq 'name', tagName + } criteria.'in'('type', tagService.domainClassFamilies[className]) criteria.cache true } - - } - else { + + } else { return Collections.EMPTY_LIST - } - } + } + } -} \ No newline at end of file +} diff --git a/src/main/groovy/grails/plugins/taggable/TaggableGrailsPlugin.groovy b/plugin/src/main/groovy/grails/plugins/taggable/TaggableGrailsPlugin.groovy similarity index 81% rename from src/main/groovy/grails/plugins/taggable/TaggableGrailsPlugin.groovy rename to plugin/src/main/groovy/grails/plugins/taggable/TaggableGrailsPlugin.groovy index bbb6677..91693dd 100644 --- a/src/main/groovy/grails/plugins/taggable/TaggableGrailsPlugin.groovy +++ b/plugin/src/main/groovy/grails/plugins/taggable/TaggableGrailsPlugin.groovy @@ -24,13 +24,9 @@ import grails.plugins.* * @author Graeme Rocher */ class TaggableGrailsPlugin extends Plugin { - def version = "2.1.0" - def grailsVersion = "3.3.0 > *" + + def grailsVersion = "7.0.0 > *" def license = 'APACHE' - def pluginExcludes = [ - "grails-app/views/error.gsp", - "grails/plugins/taggable/Test*" - ] def observe = ['hibernate'] def developers = [ @@ -42,10 +38,10 @@ A plugin that adds a generic mechanism for tagging data. ''' // URL to the plugin's documentation - def documentation = "http://grails.org/plugin/taggable" - def issueManagement = [system: "JIRA", url: "http://jira.grails.org/browse/GPTAGGABLE"] + def documentation = "https://gpc.github.io/taggable" + def issueManagement = [system: "JIRA", url: "https://github.com/gpc/grails-taggable/issues"] def scm = [url: "https://github.com/gpc/grails-taggable"] - def organization = [ name: "Grails Plugin Collective", url: "http://github.com/gpc" ] + def organization = [ name: "Grails Plugin Collective", url: "https://github.com/gpc" ] void doWithApplicationContext() { def tagService = applicationContext.taggableService diff --git a/src/test/groovy/grails/plugins/taggable/TagsTagLibSpec.groovy b/plugin/src/test/groovy/grails/plugins/taggable/TagsTagLibSpec.groovy similarity index 61% rename from src/test/groovy/grails/plugins/taggable/TagsTagLibSpec.groovy rename to plugin/src/test/groovy/grails/plugins/taggable/TagsTagLibSpec.groovy index a648ec7..10f3ba5 100644 --- a/src/test/groovy/grails/plugins/taggable/TagsTagLibSpec.groovy +++ b/plugin/src/test/groovy/grails/plugins/taggable/TagsTagLibSpec.groovy @@ -6,34 +6,32 @@ import spock.lang.Specification class TagsTagLibSpec extends Specification implements TagLibUnitTest { - def setup() { + void setup() { webRequest.controllerName = 'default' config.setAt("grails.taggable.css.classes", null) } void "test that tagCloud fails when no action attribute specified"() { when: - tagLib.tagCloud tags: [:] + applyTemplate('', [tags: [:]]) then: Exception e = thrown(GrailsTagException) - e != null - e.getMessage() == "Required attribute [action] is missing" + e.getMessage().endsWith( "Required attribute [action] is missing") } void "test that tagCloud fails when no tags attribute specified"() { when: - tagLib.tagCloud action: "browseByTag" + applyTemplate('', [action: "browseByTag"]) then: Exception e = thrown(GrailsTagException) - e != null - e.getMessage() == "Required attribute [tags] must be a map of tag names to tag counts" + e.getMessage().endsWith("Required attribute [tags] must be a map of tag names to tag counts") } void "test that tagCloud uses current controller when no attr passed"() { when: - def result = tagLib.tagCloud(tags: [hello: 1, world: 10, apple: 3, orange: 7], action: "byTag") + def result = applyTemplate('', [tags: [hello: 1, world: 10, apple: 3, orange: 7], action: "byTag"]) then: - result.toString() == "
  1. " + + result == "
    1. " + "hello
    2. " + "
    3. world
    4. " + "
    5. apple
    6. " + @@ -43,9 +41,9 @@ class TagsTagLibSpec extends Specification implements TagLibUnitTest void "test tagCloud with long counts"() { when: - def result = tagLib.tagCloud(tags: [hello: 1L, world: 10L, apple: 3L, orange: 7L], action: "byTag") + def result = applyTemplate('', [tags: [hello: 1L, world: 10L, apple: 3L, orange: 7L], action: "byTag"]) then: - result.toString() == "
      1. " + + result == "
        1. " + "hello
        2. " + "
        3. world
        4. " + "
        5. apple
        6. " + @@ -55,9 +53,9 @@ class TagsTagLibSpec extends Specification implements TagLibUnitTest void "test tagCloud with controller attribute"() { when: - def result = tagLib.tagCloud(tags: [hello: 1, world: 10, apple: 3, orange: 7], controller: "plugin", action: "byTag") + def result = applyTemplate('', [tags: [hello: 1, world: 10, apple: 3, orange: 7], controller: "plugin", action: "byTag"]) then: - result.toString() == "
          1. " + + result == "
            1. " + "hello
            2. " + "
            3. world
            4. " + "
            5. apple
            6. " + @@ -67,9 +65,9 @@ class TagsTagLibSpec extends Specification implements TagLibUnitTest void "test tagCloud with custom id property"() { when: - def result = tagLib.tagCloud(tags: [hello: 1, world: 10, apple: 3, orange: 7], controller: "plugin", action: "byTag", idProperty: "tagName") + def result = applyTemplate('', [tags: [hello: 1, world: 10, apple: 3, orange: 7], controller: "plugin", action: "byTag", idProperty: "tagName"]) then: - result.toString() == "
              1. " + + result == "
                1. " + "hello
                2. " + "
                3. world
                4. " + "
                5. apple
                6. " + @@ -86,9 +84,9 @@ class TagsTagLibSpec extends Specification implements TagLibUnitTest "four" ] when: - def result = tagLib.tagCloud(tags: [hello: 2, world: 10, apple: 3, orange: 7], controller: "plugin", action: "byTag") + def result = applyTemplate('', [tags: [hello: 2, world: 10, apple: 3, orange: 7], controller: "plugin", action: "byTag"]) then: - result.toString() == "
                  1. " + + result == "
                    1. " + "hello
                    2. " + "
                    3. world
                    4. " + "
                    5. apple
                    6. " + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..895b1d8 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,57 @@ +import org.gradle.api.initialization.resolve.RepositoriesMode + +pluginManagement { + repositories { + if (System.getenv('INCLUDE_MAVEN_LOCAL')) { + mavenLocal() + } + maven { url = 'https://repo.grails.org/grails/restricted' } + } + + includeBuild('./build-logic') { + it.name = 'build-logic' + } +} + +plugins { + id 'com.gradle.common-custom-user-data-gradle-plugin' version '2.3' +} + +def isCI = System.getenv().containsKey('CI') +def isLocal = !isCI +def isReproducibleBuild = System.getenv('SOURCE_DATE_EPOCH') != null +if (isReproducibleBuild) { + gradle.settingsEvaluated { + logger.warn( + '***** Remote Build Cache Disabled due to Reproducible Build *****\n' + + 'Build date will be set to (SOURCE_DATE_EPOCH={})', + System.getenv('SOURCE_DATE_EPOCH') + ) + } +} + +buildCache { + local { enabled = (isLocal && !isReproducibleBuild) || (isCI && isReproducibleBuild) } +} + +rootProject.name = 'taggable-root' + +include('plugin') +project(':plugin').name = 'taggable' +include('docs') +project(':docs').name = 'taggable-docs' + +file('examples').listFiles({ it.directory } as FileFilter).each { + include(it.name) + project(":$it.name").projectDir = file("examples/$it.name") +} + +dependencyResolutionManagement { + repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS + repositories { + if (System.getenv('INCLUDE_MAVEN_LOCAL')) { + mavenLocal() + } + maven { url = 'https://repo.grails.org/grails/restricted' } + } +} diff --git a/src/integration-test/groovy/grails/plugins/taggable/TaggableSpec.groovy b/src/integration-test/groovy/grails/plugins/taggable/TaggableSpec.groovy deleted file mode 100644 index 7f6d737..0000000 --- a/src/integration-test/groovy/grails/plugins/taggable/TaggableSpec.groovy +++ /dev/null @@ -1,298 +0,0 @@ -package grails.plugins.taggable - -import grails.gorm.transactions.Rollback -import grails.testing.mixin.integration.Integration -import spock.lang.Specification - -import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertTrue - -@Integration -@Rollback -class TaggableSpec extends Specification { - - def setup() { - Tag.preserveCaseForTesting = false - } - - void testAddTagMethodCaseInsensitive() { - given: - def td = new TestDomain(name: "foo") - td.save() - - when: - td.addTag("Groovy") - .addTag("grails") - and: - def links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') - - then: - assertEquals 2, links.size() - assertEquals(['groovy', 'grails'], links.tag.name) - } - - void testAddTagMethodCasePreserving() { - given: - Tag.preserveCaseForTesting = true - - def td = new TestDomain(name:"foo") - td.save() - - when: - td.addTag("Groovy") - .addTag("grails") - - def links = TagLink.findAllWhere(tagRef:td.id, type:'testDomain') - then: - assertEquals 2, links.size() - assertEquals( ['Groovy', 'grails'], links.tag.name ) - - when: - // adding a second, even if preserving case in DB it should still not add it as already has such a tag - td.addTag("groovy") - and: - links = TagLink.findAllWhere(tagRef:td.id, type:'testDomain') - - then: - assertEquals 3, links.size() - assertEquals( ['Groovy', 'grails', 'groovy'], links.tag.name ) - } - - void testAddTagsMethod() { - given: - def td = new TestDomain(name:"foo") - td.save() - - when: - td.addTags(["groovy","grails"]) - and: - def links = TagLink.findAllWhere(tagRef:td.id, type:'testDomain') - - then: - assertEquals 2, links.size() - assertEquals( ['groovy', 'grails'], links.tag.name ) - } - - void testRemoveTagMethod() { - given: - def td = new TestDomain(name: "foo") - td.save() - - when: - td.addTag("groovy") - .addTag("grails") - and: - def links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') - - then: - assertEquals 2, links.size() - assertEquals(['groovy', 'grails'], links.tag.name) - - when: - td.removeTag("groovy") - and: - links = TagLink.findAllWhere(tagRef: td.id, type: 'testDomain') - - then: - assertEquals 1, links.size() - assertEquals(['grails'], links.tag.name) - } - - void testGetTagsMethod() { - given: - def td = new TestDomain(name:"foo") - td.save() - - when: - td.addTag("groovy") - .addTag("grails") - td.save(flush:true) - - and: - TestDomain.withSession { session -> session.clear() } - td = TestDomain.findByName("foo") - - then: - assertEquals( ['groovy', 'grails'], td.tags ) - } - - void testSetTagsMethod() { - given: - def td = new TestDomain(name:"foo") - td.save() - - when: - td.tags = ["groovy", null, "grails", ''] - and: - def links = TagLink.findAllWhere(tagRef:td.id, type:'testDomain') - - then: - assertEquals 2, links.size() - assertEquals( ['groovy', 'grails'], links.tag.name ) - assertEquals( ['groovy', 'grails'], td.tags ) - - when: - td.tags = ["foo", "bar"] - and: - links = TagLink.findAllWhere(tagRef:td.id, type:'testDomain') - - then: - assertEquals 2, links.size() - assertEquals( ['foo', 'bar'].sort(true), links.tag.name.sort(true) ) - assertEquals( ['foo', 'bar'].sort(true), td.tags.sort(true) ) - - when: - td.tags = [] - and: - links = TagLink.findAllWhere(tagRef:td.id, type:'testDomain') - - then: - assertEquals 0, links.size() - assertEquals( [], links.tag.name ) - assertEquals( [], td.tags ) - } - - void testFindAllByTag() { - given: - new TestDomain(name:"foo") - .save() - .addTag("groovy") - .addTag("grails") - .addTag("griffon") - new TestDomain(name:"bar") - .save() - .addTag("groovy") - .addTag("grails") - - when: - def results = TestDomain.findAllByTag("groovy") - - then: - assertEquals 2, results.size() - assertTrue results[0] instanceof TestDomain - - assertEquals 2, TestDomain.findAllByTag("groovy").size() - assertEquals 2, TestDomain.findAllByTag("grails").size() - assertEquals 1, TestDomain.findAllByTag("griffon").size() - assertEquals 0, TestDomain.findAllByTag("nothing").size() - assertEquals 0, TestDomain.findAllByTag(null).size() - - } - - void testFindAllByTagPolymorphic() { - given: - new TestDomain(name:"foo") - .save() - .addTag("groovy") - .addTag("grails") - .addTag("griffon") - new TestDescendent(name:"bar", other:'bla') - .save() - .addTag("groovy") - .addTag("grails") - .addTag("gradle") - - when: - def results = TestDomain.findAllByTag("groovy") - - then: - assertEquals 2, results.size() - assertTrue results[0] instanceof TestDomain - - assertEquals 2, TestDomain.findAllByTag("groovy").size() - assertEquals 1, TestDescendent.findAllByTag("groovy").size() - - assertEquals 2, TestDomain.findAllByTag("grails").size() - assertEquals 1, TestDescendent.findAllByTag("grails").size() - - assertEquals 1, TestDomain.findAllByTag("gradle").size() - assertEquals 1, TestDescendent.findAllByTag("gradle").size() - - assertEquals 1, TestDomain.findAllByTag("griffon").size() - assertEquals 0, TestDomain.findAllByTag("nothing").size() - assertEquals 0, TestDomain.findAllByTag(null).size() - - } - - void testCountByTag() { - given: - new TestDomain(name:"foo") - .save() - .addTag("groovy") - .addTag("grails") - .addTag("griffon") - new TestDomain(name:"bar") - .save() - .addTag("groovy") - .addTag("grails") - new TestDescendent(name:"bla", other:'zzzz') - .save() - .addTag("groovy") - .addTag("grails") - .addTag("gradle") - - expect: - assertEquals 3, TestDomain.countByTag("groovy") - assertEquals 1, TestDescendent.countByTag("groovy") - - assertEquals 1, TestDomain.countByTag("griffon") - assertEquals 0, TestDescendent.countByTag("griffon") - - assertEquals 1, TestDomain.countByTag("gradle") - assertEquals 1, TestDescendent.countByTag("gradle") - - assertEquals 0, TestDomain.countByTag("rubbish") - assertEquals 0, TestDomain.countByTag(null) - - } - - void testAllTags() { - given: - new TestDomain(name:"foo") - .save() - .addTag("groovy") - .addTag("grails") - .addTag("griffon") - new TestDomain(name:"bar") - .save() - .addTag("groovy") - .addTag("grails") - new TestDescendent(name:"bla", other:'zzzz') - .save() - .addTag("groovy") - .addTag("grails") - .addTag("gradle") - - expect: - assertEquals( ['gradle','grails','griffon','groovy'].sort(true), TestDomain.allTags.sort(true) ) - assertEquals 4, TestDomain.totalTags - - assertEquals( ['gradle','grails','groovy'].sort(true), TestDescendent.allTags.sort(true) ) - assertEquals 3, TestDescendent.totalTags - } - - void testParseTags() { - given: - def td = new TestDomain(name:"foo") - .save() - - when: - td.parseTags("groovy,grails,griffon") - - then: - assertEquals( ['grails','griffon','groovy'], TestDomain.allTags ) - } - - void testParseTagsWithDelimiter() { - given: - def td = new TestDomain(name:"foo") - .save() - - when: - td.parseTags("groovy grails griffon", " ") - - then: - assertEquals( ['grails','griffon','groovy'], TestDomain.allTags ) - - } -} diff --git a/travis-build.sh b/travis-build.sh deleted file mode 100755 index abe9f5d..0000000 --- a/travis-build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -e -rm -rf *.zip -./gradlew clean test assemble - -filename=$(find build/libs -name "*.jar" | head -1) -filename=$(basename "$filename") - -EXIT_STATUS=0 -echo "Publishing archives for branch $TRAVIS_BRANCH" -if [[ -n $TRAVIS_TAG ]] || [[ $TRAVIS_BRANCH == 'master' && $TRAVIS_PULL_REQUEST == 'false' ]]; then - - echo "Publishing archives" - - if [[ -n $TRAVIS_TAG ]]; then - ./gradlew bintrayUpload || EXIT_STATUS=$? - else - ./gradlew publish || EXIT_STATUS=$? - fi - -fi -exit $EXIT_STATUS \ No newline at end of file