From a4e6c8798526a946c5dd71a9c2723a38ad8a05aa Mon Sep 17 00:00:00 2001 From: Alexander Tsybulsky Date: Thu, 27 Aug 2020 08:47:27 +0300 Subject: [PATCH] Switching to PRs (#3695) * string map: public entries (read only) * port http agent * draft wip * drafting * fix SSL * port ajson * integrate ajson and http_agent * agent global headers * github enumerator * update ajson * draft wip * enumerator factory * http_agent via factory * proxy * repo->switch_origin draft * refactor enum factory * Revert "refactor enum factory" This reverts commit bcfef40b38991adfe5f73677919db6bd0c652f7c. * page wiring draft * scripts draft * "let it work!" * repo setings page draft * linter * linter * linter * linter * linter * better settings page * finetune page * cleanups * change hotkey * fix ajson_error * Update src/zcl_abapgit_repo_online.clas.abap Co-authored-by: Lars Hvam * abaplint nesting 6 * remove description * back to good old popups * linter * move commands to branches dropdown * auth helpers * move json to separate package Co-authored-by: Lars Hvam --- abaplint.json | 2 +- src/http/zcl_abapgit_http_agent.clas.abap | 152 +++ ...cl_abapgit_http_agent.clas.locals_imp.abap | 79 ++ src/http/zcl_abapgit_http_agent.clas.xml | 16 + src/http/zcl_abapgit_pr_enum_github.clas.abap | 139 ++ src/http/zcl_abapgit_pr_enum_github.clas.xml | 16 + src/http/zcl_abapgit_pr_enumerator.clas.abap | 130 ++ src/http/zcl_abapgit_pr_enumerator.clas.xml | 16 + src/http/zif_abapgit_http_agent.intf.abap | 29 + src/http/zif_abapgit_http_agent.intf.xml | 15 + src/http/zif_abapgit_http_response.intf.abap | 29 + src/http/zif_abapgit_http_response.intf.xml | 15 + .../zif_abapgit_pr_enum_provider.intf.abap | 24 + .../zif_abapgit_pr_enum_provider.intf.xml | 15 + src/json/package.devc.xml | 10 + src/json/zcl_abapgit_ajson.clas.abap | 292 ++++ .../zcl_abapgit_ajson.clas.locals_imp.abap | 398 ++++++ .../zcl_abapgit_ajson.clas.testclasses.abap | 1191 +++++++++++++++++ src/json/zcl_abapgit_ajson.clas.xml | 17 + src/json/zcx_abapgit_ajson_error.clas.abap | 91 ++ ..._abapgit_ajson_error.clas.testclasses.abap | 55 + src/json/zcx_abapgit_ajson_error.clas.xml | 19 + src/json/zif_abapgit_ajson_reader.intf.abap | 62 + src/json/zif_abapgit_ajson_reader.intf.xml | 15 + src/persist/zif_abapgit_persistence.intf.abap | 2 + .../zcl_abapgit_gui_page_repo_sett.clas.abap | 58 + .../zcl_abapgit_gui_page_view_repo.clas.abap | 102 +- src/ui/zcl_abapgit_popups.clas.abap | 45 + ..._abapgit_ui_injector.clas.testclasses.abap | 4 + src/ui/zif_abapgit_popups.intf.abap | 7 + src/utils/zcl_abapgit_login_manager.clas.abap | 19 + src/utils/zcl_abapgit_string_map.clas.abap | 3 +- src/zcl_abapgit_factory.clas.abap | 15 + src/zcl_abapgit_injector.clas.abap | 8 + src/zcl_abapgit_repo.clas.abap | 11 +- src/zcl_abapgit_repo_online.clas.abap | 53 + 36 files changed, 3144 insertions(+), 10 deletions(-) create mode 100644 src/http/zcl_abapgit_http_agent.clas.abap create mode 100644 src/http/zcl_abapgit_http_agent.clas.locals_imp.abap create mode 100644 src/http/zcl_abapgit_http_agent.clas.xml create mode 100644 src/http/zcl_abapgit_pr_enum_github.clas.abap create mode 100644 src/http/zcl_abapgit_pr_enum_github.clas.xml create mode 100644 src/http/zcl_abapgit_pr_enumerator.clas.abap create mode 100644 src/http/zcl_abapgit_pr_enumerator.clas.xml create mode 100644 src/http/zif_abapgit_http_agent.intf.abap create mode 100644 src/http/zif_abapgit_http_agent.intf.xml create mode 100644 src/http/zif_abapgit_http_response.intf.abap create mode 100644 src/http/zif_abapgit_http_response.intf.xml create mode 100644 src/http/zif_abapgit_pr_enum_provider.intf.abap create mode 100644 src/http/zif_abapgit_pr_enum_provider.intf.xml create mode 100644 src/json/package.devc.xml create mode 100644 src/json/zcl_abapgit_ajson.clas.abap create mode 100644 src/json/zcl_abapgit_ajson.clas.locals_imp.abap create mode 100644 src/json/zcl_abapgit_ajson.clas.testclasses.abap create mode 100644 src/json/zcl_abapgit_ajson.clas.xml create mode 100644 src/json/zcx_abapgit_ajson_error.clas.abap create mode 100644 src/json/zcx_abapgit_ajson_error.clas.testclasses.abap create mode 100644 src/json/zcx_abapgit_ajson_error.clas.xml create mode 100644 src/json/zif_abapgit_ajson_reader.intf.abap create mode 100644 src/json/zif_abapgit_ajson_reader.intf.xml diff --git a/abaplint.json b/abaplint.json index d16c4ffda..f178dc849 100644 --- a/abaplint.json +++ b/abaplint.json @@ -248,7 +248,7 @@ "mix_returning": true, "msag_consistency": true, "nesting": { - "depth": 5 + "depth": 6 }, "no_public_attributes": false, "object_naming": { diff --git a/src/http/zcl_abapgit_http_agent.clas.abap b/src/http/zcl_abapgit_http_agent.clas.abap new file mode 100644 index 000000000..6e85e1cb6 --- /dev/null +++ b/src/http/zcl_abapgit_http_agent.clas.abap @@ -0,0 +1,152 @@ +CLASS zcl_abapgit_http_agent DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + INTERFACES zif_abapgit_http_agent . + + CLASS-METHODS create + RETURNING + VALUE(ri_instance) TYPE REF TO zif_abapgit_http_agent . + + METHODS constructor. + + PROTECTED SECTION. + PRIVATE SECTION. + + DATA mo_global_headers TYPE REF TO zcl_abapgit_string_map. + + CLASS-METHODS attach_payload + IMPORTING + ii_request TYPE REF TO if_http_request + iv_payload TYPE any + RAISING + zcx_abapgit_exception. + +ENDCLASS. + + + +CLASS ZCL_ABAPGIT_HTTP_AGENT IMPLEMENTATION. + + + METHOD attach_payload. + + DATA lo_type TYPE REF TO cl_abap_typedescr. + lo_type = cl_abap_typedescr=>describe_by_data( iv_payload ). + + IF lo_type->type_kind = cl_abap_typedescr=>typekind_xstring. + ii_request->set_data( iv_payload ). + + ELSEIF lo_type->type_kind = cl_abap_typedescr=>typekind_string. + ii_request->set_cdata( iv_payload ). + + ELSE. + zcx_abapgit_exception=>raise( |Unexpected payload type { lo_type->absolute_name }| ). + ENDIF. + + ENDMETHOD. + + + METHOD constructor. + + CREATE OBJECT mo_global_headers. + + ENDMETHOD. + + + METHOD create. + + CREATE OBJECT ri_instance TYPE zcl_abapgit_http_agent. + + ENDMETHOD. + + + METHOD zif_abapgit_http_agent~global_headers. + + ro_global_headers = mo_global_headers. + + ENDMETHOD. + + + METHOD zif_abapgit_http_agent~request. + + DATA li_client TYPE REF TO if_http_client. + DATA lo_proxy_configuration TYPE REF TO zcl_abapgit_proxy_config. + DATA lv_code TYPE i. + DATA lv_message TYPE string. + FIELD-SYMBOLS LIKE LINE OF io_query->mt_entries. + + CREATE OBJECT lo_proxy_configuration. + + cl_http_client=>create_by_url( + EXPORTING + url = iv_url + ssl_id = zcl_abapgit_exit=>get_instance( )->get_ssl_id( ) + proxy_host = lo_proxy_configuration->get_proxy_url( iv_url ) + proxy_service = lo_proxy_configuration->get_proxy_port( iv_url ) + IMPORTING + client = li_client ). + + li_client->request->set_version( if_http_request=>co_protocol_version_1_1 ). + li_client->request->set_method( iv_method ). + + IF io_query IS BOUND. + LOOP AT io_query->mt_entries ASSIGNING . + li_client->request->set_form_field( + name = -k + value = -v ). + ENDLOOP. + ENDIF. + + LOOP AT mo_global_headers->mt_entries ASSIGNING . + li_client->request->set_header_field( + name = to_lower( -k ) + value = -v ). + ENDLOOP. + + IF io_headers IS BOUND. + LOOP AT io_headers->mt_entries ASSIGNING . + li_client->request->set_header_field( + name = to_lower( -k ) + value = -v ). + ENDLOOP. + ENDIF. + + IF iv_method = zif_abapgit_http_agent=>c_methods-post + OR iv_method = zif_abapgit_http_agent=>c_methods-put + OR iv_method = zif_abapgit_http_agent=>c_methods-patch. + attach_payload( + ii_request = li_client->request + iv_payload = iv_payload ). + ENDIF. + + li_client->send( + EXCEPTIONS + http_communication_failure = 1 + http_invalid_state = 2 + http_processing_failed = 3 + http_invalid_timeout = 4 + OTHERS = 5 ). + IF sy-subrc = 0. + li_client->receive( + EXCEPTIONS + http_communication_failure = 1 + http_invalid_state = 2 + http_processing_failed = 3 + OTHERS = 4 ). + ENDIF. + + IF sy-subrc <> 0. + li_client->get_last_error( + IMPORTING + code = lv_code + message = lv_message ). + zcx_abapgit_exception=>raise( |HTTP error: [{ lv_code }] { lv_message }| ). + ENDIF. + + ri_response = lcl_http_response=>create( li_client ). + + ENDMETHOD. +ENDCLASS. diff --git a/src/http/zcl_abapgit_http_agent.clas.locals_imp.abap b/src/http/zcl_abapgit_http_agent.clas.locals_imp.abap new file mode 100644 index 000000000..08df2d526 --- /dev/null +++ b/src/http/zcl_abapgit_http_agent.clas.locals_imp.abap @@ -0,0 +1,79 @@ +CLASS lcl_http_response DEFINITION FINAL. + PUBLIC SECTION. + + INTERFACES zif_abapgit_http_response. + + CLASS-METHODS create + IMPORTING + ii_client TYPE REF TO if_http_client + RETURNING + VALUE(ri_response) TYPE REF TO zif_abapgit_http_response. + + PRIVATE SECTION. + DATA mi_client TYPE REF TO if_http_client. + DATA mi_response TYPE REF TO if_http_response. +ENDCLASS. + +CLASS lcl_http_response IMPLEMENTATION. + + METHOD create. + DATA lo_response TYPE REF TO lcl_http_response. + CREATE OBJECT lo_response. + lo_response->mi_client = ii_client. + lo_response->mi_response = ii_client->response. + ri_response ?= lo_response. + ENDMETHOD. + + METHOD zif_abapgit_http_response~close. + mi_client->close( ). + ENDMETHOD. + + METHOD zif_abapgit_http_response~is_ok. + DATA lv_code TYPE i. + lv_code = zif_abapgit_http_response~code( ). + rv_yes = boolc( lv_code >= 200 AND lv_code < 300 ). + ENDMETHOD. + + METHOD zif_abapgit_http_response~data. + rv_data = mi_response->get_data( ). + ENDMETHOD. + + METHOD zif_abapgit_http_response~cdata. + rv_data = mi_response->get_cdata( ). + ENDMETHOD. + + METHOD zif_abapgit_http_response~code. + DATA lv_msg TYPE string ##NEEDED. + mi_response->get_status( + IMPORTING + reason = lv_msg " for debug + code = rv_code ). + ENDMETHOD. + + METHOD zif_abapgit_http_response~json. + + ri_json = zcl_abapgit_ajson=>parse( zif_abapgit_http_response~cdata( ) ). + + ENDMETHOD. + + METHOD zif_abapgit_http_response~error. + rv_message = mi_response->get_cdata( ). + ENDMETHOD. + + METHOD zif_abapgit_http_response~headers. + + DATA lt_headers TYPE tihttpnvp. + FIELD-SYMBOLS LIKE LINE OF lt_headers. + + CREATE OBJECT ro_headers. + + mi_response->get_header_fields( CHANGING fields = lt_headers ). + LOOP AT lt_headers ASSIGNING . + ro_headers->set( + iv_key = -name + iv_val = -value ). + ENDLOOP. + + ENDMETHOD. + +ENDCLASS. diff --git a/src/http/zcl_abapgit_http_agent.clas.xml b/src/http/zcl_abapgit_http_agent.clas.xml new file mode 100644 index 000000000..3893c3e76 --- /dev/null +++ b/src/http/zcl_abapgit_http_agent.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_ABAPGIT_HTTP_AGENT + E + abapGit http agent + 1 + X + X + X + + + + diff --git a/src/http/zcl_abapgit_pr_enum_github.clas.abap b/src/http/zcl_abapgit_pr_enum_github.clas.abap new file mode 100644 index 000000000..bd5bf8ad9 --- /dev/null +++ b/src/http/zcl_abapgit_pr_enum_github.clas.abap @@ -0,0 +1,139 @@ +CLASS zcl_abapgit_pr_enum_github DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + + INTERFACES zif_abapgit_pr_enum_provider . + + METHODS constructor + IMPORTING + !iv_user_and_repo TYPE string + !ii_http_agent TYPE REF TO zif_abapgit_http_agent + RAISING + zcx_abapgit_exception. + PROTECTED SECTION. + PRIVATE SECTION. + + TYPES: + BEGIN OF ty_info, + repo_json TYPE REF TO zif_abapgit_ajson_reader, + pulls TYPE zif_abapgit_pr_enum_provider=>tty_pulls, + END OF ty_info. + + DATA mi_http_agent TYPE REF TO zif_abapgit_http_agent. + DATA mv_repo_url TYPE string. + + METHODS fetch_repo_by_url + IMPORTING + iv_repo_url TYPE string + RETURNING + VALUE(rs_info) TYPE ty_info + RAISING + zcx_abapgit_exception. + + METHODS convert_list + IMPORTING + ii_json TYPE REF TO zif_abapgit_ajson_reader + RETURNING + VALUE(rt_pulls) TYPE zif_abapgit_pr_enum_provider=>tty_pulls. + + METHODS clean_url + IMPORTING + iv_url TYPE string + RETURNING + VALUE(rv_url) TYPE string. + +ENDCLASS. + + + +CLASS ZCL_ABAPGIT_PR_ENUM_GITHUB IMPLEMENTATION. + + + METHOD clean_url. + rv_url = replace( + val = iv_url + regex = '\{.*\}$' + with = '' ). + ENDMETHOD. + + + METHOD constructor. + + mv_repo_url = |https://api.github.com/repos/{ iv_user_and_repo }|. + mi_http_agent = ii_http_agent. + mi_http_agent->global_headers( )->set( + iv_key = 'Accept' + iv_val = 'application/vnd.github.v3+json' ). + + IF zcl_abapgit_login_manager=>get( mv_repo_url ) IS NOT INITIAL. + mi_http_agent->global_headers( )->set( + iv_key = 'Authorization' + iv_val = zcl_abapgit_login_manager=>get( mv_repo_url ) ). + ENDIF. + + ENDMETHOD. + + + METHOD convert_list. + + DATA lt_items TYPE string_table. + DATA lv_i TYPE string. + FIELD-SYMBOLS LIKE LINE OF rt_pulls. + + lt_items = ii_json->members( '/' ). + + LOOP AT lt_items INTO lv_i. + APPEND INITIAL LINE TO rt_pulls ASSIGNING . + -base_url = ii_json->get( |/{ lv_i }/base/repo/clone_url| ). + -number = ii_json->get( |/{ lv_i }/number| ). + -title = ii_json->get( |/{ lv_i }/title| ). + -user = ii_json->get( |/{ lv_i }/user/login| ). + -head_url = ii_json->get( |/{ lv_i }/head/repo/clone_url| ). + -head_branch = ii_json->get( |/{ lv_i }/head/ref| ). + -created_at = ii_json->get( |/{ lv_i }/created_at| ). + ENDLOOP. + + ENDMETHOD. + + + METHOD fetch_repo_by_url. + + DATA li_pulls_json TYPE REF TO zif_abapgit_ajson_reader. + DATA lv_pull_url TYPE string. + DATA li_response TYPE REF TO zif_abapgit_http_response. + + li_response = mi_http_agent->request( iv_repo_url ). + rs_info-repo_json = li_response->json( ). + li_response->headers( ). " for debug + + lv_pull_url = clean_url( rs_info-repo_json->get( '/pulls_url' ) ). + li_pulls_json = mi_http_agent->request( lv_pull_url )->json( ). + rs_info-pulls = convert_list( li_pulls_json ). + + ENDMETHOD. + + + METHOD zif_abapgit_pr_enum_provider~list_pull_requests. + + DATA lv_repo_url TYPE string. + DATA lv_upstream_url TYPE string. + DATA ls_repo_info TYPE ty_info. + FIELD-SYMBOLS LIKE LINE OF ls_repo_info-pulls. + + ls_repo_info = fetch_repo_by_url( mv_repo_url ). + APPEND LINES OF ls_repo_info-pulls TO rt_pulls. + + IF ls_repo_info-repo_json->get_boolean( '/fork' ) = abap_true. + lv_upstream_url = ls_repo_info-repo_json->get( '/source/url' ). " parent ? + ls_repo_info = fetch_repo_by_url( lv_upstream_url ). + LOOP AT ls_repo_info-pulls ASSIGNING . + -is_for_upstream = abap_true. + APPEND TO rt_pulls. + ENDLOOP. + ENDIF. + + ENDMETHOD. +ENDCLASS. diff --git a/src/http/zcl_abapgit_pr_enum_github.clas.xml b/src/http/zcl_abapgit_pr_enum_github.clas.xml new file mode 100644 index 000000000..0a11817fe --- /dev/null +++ b/src/http/zcl_abapgit_pr_enum_github.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_ABAPGIT_PR_ENUM_GITHUB + E + abapgit Github PR enumerator strategy + 1 + X + X + X + + + + diff --git a/src/http/zcl_abapgit_pr_enumerator.clas.abap b/src/http/zcl_abapgit_pr_enumerator.clas.abap new file mode 100644 index 000000000..a4c71674c --- /dev/null +++ b/src/http/zcl_abapgit_pr_enumerator.clas.abap @@ -0,0 +1,130 @@ +CLASS zcl_abapgit_pr_enumerator DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + + METHODS constructor + IMPORTING + io_repo TYPE REF TO zcl_abapgit_repo + RAISING + zcx_abapgit_exception. + + METHODS has_pulls + RETURNING + VALUE(rv_yes) TYPE abap_bool + RAISING + zcx_abapgit_exception. + + METHODS get_pulls + RETURNING + VALUE(rt_pulls) TYPE zif_abapgit_pr_enum_provider=>tty_pulls + RAISING + zcx_abapgit_exception. + + CLASS-METHODS new + IMPORTING + io_repo TYPE REF TO zcl_abapgit_repo + RETURNING + VALUE(ro_instance) TYPE REF TO zcl_abapgit_pr_enumerator + RAISING + zcx_abapgit_exception. + + PROTECTED SECTION. + PRIVATE SECTION. + DATA mv_repo_url TYPE string. + DATA mi_enum_provider TYPE REF TO zif_abapgit_pr_enum_provider. + + CLASS-METHODS create_provider + IMPORTING + iv_repo_url TYPE string + RETURNING + VALUE(ri_provider) TYPE REF TO zif_abapgit_pr_enum_provider + RAISING + zcx_abapgit_exception. + +ENDCLASS. + + + +CLASS ZCL_ABAPGIT_PR_ENUMERATOR IMPLEMENTATION. + + + METHOD constructor. + + DATA lo_repo_online TYPE REF TO zcl_abapgit_repo_online. + + IF io_repo IS NOT BOUND OR io_repo->is_offline( ) = abap_true. + RETURN. + ENDIF. + + lo_repo_online ?= io_repo. + mv_repo_url = to_lower( lo_repo_online->get_url( ) ). + TRY. + mi_enum_provider = create_provider( mv_repo_url ). + CATCH zcx_abapgit_exception. + ENDTRY. + + ENDMETHOD. + + + METHOD create_provider. + + DATA li_agent TYPE REF TO zif_abapgit_http_agent. + DATA lv_user TYPE string. + DATA lv_repo TYPE string. + + li_agent = zcl_abapgit_factory=>get_http_agent( ). + + FIND ALL OCCURRENCES OF REGEX 'github\.com\/([^\/]+)\/([^\/]+)' + IN iv_repo_url + SUBMATCHES lv_user lv_repo. + IF sy-subrc = 0. + lv_repo = replace( + val = lv_repo + regex = '\.git$' + with = '' ). + CREATE OBJECT ri_provider TYPE zcl_abapgit_pr_enum_github + EXPORTING + iv_user_and_repo = |{ lv_user }/{ lv_repo }| + ii_http_agent = li_agent. + ELSE. + zcx_abapgit_exception=>raise( |PR enumeration is not supported for { iv_repo_url }| ). + ENDIF. + + " TODO somewhen more providers + + ENDMETHOD. + + + METHOD get_pulls. + + IF mi_enum_provider IS NOT BOUND. + RETURN. + ENDIF. + + rt_pulls = mi_enum_provider->list_pull_requests( ). + + " TODO caching ? + + ENDMETHOD. + + + METHOD has_pulls. + + IF mi_enum_provider IS NOT BOUND. + RETURN. " false + ENDIF. + + IF get_pulls( ) IS NOT INITIAL. + rv_yes = abap_true. + ENDIF. + + ENDMETHOD. + + + METHOD new. + CREATE OBJECT ro_instance EXPORTING io_repo = io_repo. + ENDMETHOD. +ENDCLASS. diff --git a/src/http/zcl_abapgit_pr_enumerator.clas.xml b/src/http/zcl_abapgit_pr_enumerator.clas.xml new file mode 100644 index 000000000..4d6c9205f --- /dev/null +++ b/src/http/zcl_abapgit_pr_enumerator.clas.xml @@ -0,0 +1,16 @@ + + + + + + ZCL_ABAPGIT_PR_ENUMERATOR + E + abapGit PR enumerator + 1 + X + X + X + + + + diff --git a/src/http/zif_abapgit_http_agent.intf.abap b/src/http/zif_abapgit_http_agent.intf.abap new file mode 100644 index 000000000..dc3615c76 --- /dev/null +++ b/src/http/zif_abapgit_http_agent.intf.abap @@ -0,0 +1,29 @@ +INTERFACE zif_abapgit_http_agent + PUBLIC . + + CONSTANTS: + BEGIN OF c_methods, + get TYPE string VALUE 'GET', + post TYPE string VALUE 'POST', + put TYPE string VALUE 'PUT', + delete TYPE string VALUE 'DELETE', + patch TYPE string VALUE 'PATCH', + END OF c_methods. + + METHODS global_headers + RETURNING + VALUE(ro_global_headers) TYPE REF TO zcl_abapgit_string_map. + + METHODS request + IMPORTING + !iv_url TYPE string + !iv_method TYPE string DEFAULT c_methods-get + !io_query TYPE REF TO zcl_abapgit_string_map OPTIONAL + !io_headers TYPE REF TO zcl_abapgit_string_map OPTIONAL + !iv_payload TYPE any OPTIONAL " can be string, xstring + RETURNING + VALUE(ri_response) TYPE REF TO zif_abapgit_http_response + RAISING + zcx_abapgit_exception . + +ENDINTERFACE. diff --git a/src/http/zif_abapgit_http_agent.intf.xml b/src/http/zif_abapgit_http_agent.intf.xml new file mode 100644 index 000000000..4fe9b25e2 --- /dev/null +++ b/src/http/zif_abapgit_http_agent.intf.xml @@ -0,0 +1,15 @@ + + + + + + ZIF_ABAPGIT_HTTP_AGENT + E + abapGit HTTP agent + 2 + 1 + X + + + + diff --git a/src/http/zif_abapgit_http_response.intf.abap b/src/http/zif_abapgit_http_response.intf.abap new file mode 100644 index 000000000..12a78f4ad --- /dev/null +++ b/src/http/zif_abapgit_http_response.intf.abap @@ -0,0 +1,29 @@ +INTERFACE zif_abapgit_http_response + PUBLIC . + + METHODS data + RETURNING + VALUE(rv_data) TYPE xstring . + METHODS cdata + RETURNING + VALUE(rv_data) TYPE string . + METHODS json + RETURNING + VALUE(ri_json) TYPE REF TO zif_abapgit_ajson_reader + RAISING + zcx_abapgit_ajson_error. + METHODS is_ok + RETURNING + VALUE(rv_yes) TYPE abap_bool . + METHODS code + RETURNING + VALUE(rv_code) TYPE i . + METHODS error + RETURNING + VALUE(rv_message) TYPE string . + METHODS headers + RETURNING + VALUE(ro_headers) TYPE REF TO zcl_abapgit_string_map . + METHODS close . + +ENDINTERFACE. diff --git a/src/http/zif_abapgit_http_response.intf.xml b/src/http/zif_abapgit_http_response.intf.xml new file mode 100644 index 000000000..514dd66d2 --- /dev/null +++ b/src/http/zif_abapgit_http_response.intf.xml @@ -0,0 +1,15 @@ + + + + + + ZIF_ABAPGIT_HTTP_RESPONSE + E + abapGit http response + 2 + 1 + X + + + + diff --git a/src/http/zif_abapgit_pr_enum_provider.intf.abap b/src/http/zif_abapgit_pr_enum_provider.intf.abap new file mode 100644 index 000000000..4660c5cfc --- /dev/null +++ b/src/http/zif_abapgit_pr_enum_provider.intf.abap @@ -0,0 +1,24 @@ +INTERFACE zif_abapgit_pr_enum_provider + PUBLIC . + + TYPES: + BEGIN OF ty_pull_request, + base_url TYPE string, + number TYPE string, + title TYPE string, + user TYPE string, + head_url TYPE string, + head_branch TYPE string, + created_at TYPE string, " TODO change to D after date parsing fixed + is_for_upstream TYPE abap_bool, + END OF ty_pull_request. + TYPES: + tty_pulls TYPE STANDARD TABLE OF ty_pull_request WITH KEY base_url number. + + METHODS list_pull_requests + RETURNING + VALUE(rt_pulls) TYPE tty_pulls + RAISING + zcx_abapgit_exception. + +ENDINTERFACE. diff --git a/src/http/zif_abapgit_pr_enum_provider.intf.xml b/src/http/zif_abapgit_pr_enum_provider.intf.xml new file mode 100644 index 000000000..1467f2d6e --- /dev/null +++ b/src/http/zif_abapgit_pr_enum_provider.intf.xml @@ -0,0 +1,15 @@ + + + + + + ZIF_ABAPGIT_PR_ENUM_PROVIDER + E + abapgit PR enumerator provider + 2 + 1 + X + + + + diff --git a/src/json/package.devc.xml b/src/json/package.devc.xml new file mode 100644 index 000000000..06e65f5cf --- /dev/null +++ b/src/json/package.devc.xml @@ -0,0 +1,10 @@ + + + + + + abapGit JSON + + + + diff --git a/src/json/zcl_abapgit_ajson.clas.abap b/src/json/zcl_abapgit_ajson.clas.abap new file mode 100644 index 000000000..a1ba13f5f --- /dev/null +++ b/src/json/zcl_abapgit_ajson.clas.abap @@ -0,0 +1,292 @@ +CLASS zcl_abapgit_ajson DEFINITION + PUBLIC + FINAL + CREATE PUBLIC . + + PUBLIC SECTION. + + CONSTANTS ported_from_url TYPE string VALUE 'https://github.com/sbcgua/ajson'. + + INTERFACES zif_abapgit_ajson_reader . + + TYPES: + BEGIN OF ty_node, + path TYPE string, + name TYPE string, + type TYPE string, + value TYPE string, + index TYPE i, + children TYPE i, + END OF ty_node . + TYPES: + ty_nodes_tt TYPE STANDARD TABLE OF ty_node WITH KEY path name . + TYPES: + ty_nodes_ts TYPE SORTED TABLE OF ty_node + WITH UNIQUE KEY path name + WITH NON-UNIQUE SORTED KEY array_index COMPONENTS path index . + TYPES: + BEGIN OF ty_path_name, + path TYPE string, + name TYPE string, + END OF ty_path_name. + + CLASS-METHODS parse + IMPORTING + !iv_json TYPE string + !iv_freeze TYPE abap_bool DEFAULT abap_false + RETURNING + VALUE(ro_instance) TYPE REF TO zcl_abapgit_ajson + RAISING + zcx_abapgit_ajson_error . + + METHODS freeze. + + PROTECTED SECTION. + PRIVATE SECTION. + + TYPES: + tty_node_stack TYPE STANDARD TABLE OF REF TO ty_node WITH DEFAULT KEY. + + DATA mt_json_tree TYPE ty_nodes_ts. + DATA mv_read_only TYPE abap_bool. + + METHODS get_item + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_item) TYPE REF TO ty_node. + +ENDCLASS. + + + +CLASS ZCL_ABAPGIT_AJSON IMPLEMENTATION. + + + METHOD freeze. + mv_read_only = abap_true. + ENDMETHOD. + + + METHOD get_item. + + FIELD-SYMBOLS LIKE LINE OF mt_json_tree. + DATA ls_path_name TYPE ty_path_name. + ls_path_name = lcl_utils=>split_path( iv_path ). + + READ TABLE mt_json_tree + ASSIGNING + WITH KEY + path = ls_path_name-path + name = ls_path_name-name. + IF sy-subrc = 0. + GET REFERENCE OF INTO rv_item. + ENDIF. + + ENDMETHOD. + + + METHOD parse. + + DATA lo_parser TYPE REF TO lcl_json_parser. + + CREATE OBJECT ro_instance. + CREATE OBJECT lo_parser. + ro_instance->mt_json_tree = lo_parser->parse( iv_json ). + + IF iv_freeze = abap_true. + ro_instance->freeze( ). + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~array_to_string_table. + + DATA lv_normalized_path TYPE string. + DATA lr_node TYPE REF TO ty_node. + DATA lv_tmp TYPE string. + FIELD-SYMBOLS LIKE LINE OF mt_json_tree. + + lv_normalized_path = lcl_utils=>normalize_path( iv_path ). + lr_node = get_item( iv_path ). + + IF lr_node IS INITIAL. + zcx_abapgit_ajson_error=>raise_json( |Path not found: { iv_path }| ). + ENDIF. + IF lr_node->type <> 'array'. + zcx_abapgit_ajson_error=>raise_json( |Array expected at: { iv_path }| ). + ENDIF. + + LOOP AT mt_json_tree ASSIGNING WHERE path = lv_normalized_path. + CASE -type. + WHEN 'num' OR 'str'. + APPEND -value TO rt_string_table. + WHEN 'null'. + APPEND '' TO rt_string_table. + WHEN 'bool'. + IF -value = 'true'. + lv_tmp = abap_true. + ELSE. + CLEAR lv_tmp. + ENDIF. + APPEND lv_tmp TO rt_string_table. + WHEN OTHERS. + zcx_abapgit_ajson_error=>raise_json( + |Cannot convert [{ -type }] to string at [{ -path }{ -name }]| ). + ENDCASE. + ENDLOOP. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~exists. + + DATA lv_item TYPE REF TO ty_node. + lv_item = get_item( iv_path ). + IF lv_item IS NOT INITIAL. + rv_exists = abap_true. + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~get. + + DATA lv_item TYPE REF TO ty_node. + lv_item = get_item( iv_path ). + IF lv_item IS NOT INITIAL. + rv_value = lv_item->value. + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~get_boolean. + + DATA lv_item TYPE REF TO ty_node. + lv_item = get_item( iv_path ). + IF lv_item IS INITIAL OR lv_item->type = 'null'. + RETURN. + ELSEIF lv_item->type = 'bool'. + rv_value = boolc( lv_item->value = 'true' ). + ELSEIF lv_item->value IS NOT INITIAL. + rv_value = abap_true. + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~get_date. + + DATA lv_item TYPE REF TO ty_node. + DATA lv_y TYPE c LENGTH 4. + DATA lv_m TYPE c LENGTH 2. + DATA lv_d TYPE c LENGTH 2. + + lv_item = get_item( iv_path ). + + IF lv_item IS NOT INITIAL AND lv_item->type = 'str'. + FIND FIRST OCCURRENCE OF REGEX '^(\d{4})-(\d{2})-(\d{2})(T|$)' + IN lv_item->value + SUBMATCHES lv_y lv_m lv_d. + CONCATENATE lv_y lv_m lv_d INTO rv_value. + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~get_integer. + + DATA lv_item TYPE REF TO ty_node. + lv_item = get_item( iv_path ). + IF lv_item IS NOT INITIAL AND lv_item->type = 'num'. + rv_value = lv_item->value. + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~get_number. + + DATA lv_item TYPE REF TO ty_node. + lv_item = get_item( iv_path ). + IF lv_item IS NOT INITIAL AND lv_item->type = 'num'. + rv_value = lv_item->value. + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~get_string. + + DATA lv_item TYPE REF TO ty_node. + lv_item = get_item( iv_path ). + IF lv_item IS NOT INITIAL AND lv_item->type <> 'null'. + rv_value = lv_item->value. + ENDIF. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~members. + + DATA lv_normalized_path TYPE string. + FIELD-SYMBOLS LIKE LINE OF mt_json_tree. + + lv_normalized_path = lcl_utils=>normalize_path( iv_path ). + + LOOP AT mt_json_tree ASSIGNING WHERE path = lv_normalized_path. + APPEND -name TO rt_members. + ENDLOOP. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~slice. + + DATA lo_section TYPE REF TO zcl_abapgit_ajson. + DATA ls_item LIKE LINE OF mt_json_tree. + DATA lv_normalized_path TYPE string. + DATA ls_path_parts TYPE ty_path_name. + DATA lv_path_len TYPE i. + + CREATE OBJECT lo_section. + lv_normalized_path = lcl_utils=>normalize_path( iv_path ). + lv_path_len = strlen( lv_normalized_path ). + ls_path_parts = lcl_utils=>split_path( lv_normalized_path ). + + LOOP AT mt_json_tree INTO ls_item. + " TODO potentially improve performance due to sorted tree (all path started from same prefix go in a row) + IF strlen( ls_item-path ) >= lv_path_len + AND substring( + val = ls_item-path + len = lv_path_len ) = lv_normalized_path. + ls_item-path = substring( + val = ls_item-path + off = lv_path_len - 1 ). " less closing '/' + INSERT ls_item INTO TABLE lo_section->mt_json_tree. + ELSEIF ls_item-path = ls_path_parts-path AND ls_item-name = ls_path_parts-name. + CLEAR: ls_item-path, ls_item-name. " this becomes a new root + INSERT ls_item INTO TABLE lo_section->mt_json_tree. + ENDIF. + ENDLOOP. + + ri_json = lo_section. + + ENDMETHOD. + + + METHOD zif_abapgit_ajson_reader~to_abap. + + DATA lo_to_abap TYPE REF TO lcl_json_to_abap. + + CLEAR ev_container. + lcl_json_to_abap=>bind( + CHANGING + cv_obj = ev_container + co_instance = lo_to_abap ). + lo_to_abap->to_abap( mt_json_tree ). + + ENDMETHOD. +ENDCLASS. diff --git a/src/json/zcl_abapgit_ajson.clas.locals_imp.abap b/src/json/zcl_abapgit_ajson.clas.locals_imp.abap new file mode 100644 index 000000000..e3876ece9 --- /dev/null +++ b/src/json/zcl_abapgit_ajson.clas.locals_imp.abap @@ -0,0 +1,398 @@ +********************************************************************** +* UTILS +********************************************************************** + +CLASS lcl_utils DEFINITION FINAL. + PUBLIC SECTION. + + CLASS-METHODS normalize_path + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_path) TYPE string. + CLASS-METHODS split_path + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_path_name) TYPE zcl_abapgit_ajson=>ty_path_name. + +ENDCLASS. + +CLASS lcl_utils IMPLEMENTATION. + + METHOD normalize_path. + + rv_path = iv_path. + IF strlen( rv_path ) = 0. + rv_path = '/'. + ENDIF. + IF rv_path+0(1) <> '/'. + rv_path = '/' && rv_path. + ENDIF. + IF substring( + val = rv_path + off = strlen( rv_path ) - 1 ) <> '/'. + rv_path = rv_path && '/'. + ENDIF. + + ENDMETHOD. + + METHOD split_path. + + DATA lv_offs TYPE i. + DATA lv_len TYPE i. + DATA lv_trim_slash TYPE i. + + lv_len = strlen( iv_path ). + IF lv_len = 0 OR iv_path = '/'. + RETURN. " empty path is the alias for root item = '' + '' + ENDIF. + + IF substring( + val = iv_path + off = lv_len - 1 ) = '/'. + lv_trim_slash = 1. " ignore last '/' + ENDIF. + + lv_offs = find( + val = reverse( iv_path ) + sub = '/' + off = lv_trim_slash ). + IF lv_offs = -1. + lv_offs = lv_len. " treat whole string as the 'name' part + ENDIF. + lv_offs = lv_len - lv_offs. + + rv_path_name-path = normalize_path( substring( + val = iv_path + len = lv_offs ) ). + rv_path_name-name = substring( + val = iv_path + off = lv_offs + len = lv_len - lv_offs - lv_trim_slash ). + + ENDMETHOD. + +ENDCLASS. + + +********************************************************************** +* PARSER +********************************************************************** + +CLASS lcl_json_parser DEFINITION FINAL. + PUBLIC SECTION. + + METHODS parse + IMPORTING + iv_json TYPE string + RETURNING + VALUE(rt_json_tree) TYPE zcl_abapgit_ajson=>ty_nodes_tt + RAISING + zcx_abapgit_ajson_error. + + PRIVATE SECTION. + + TYPES: + ty_stack_tt TYPE STANDARD TABLE OF REF TO zcl_abapgit_ajson=>ty_node. + + DATA mt_stack TYPE ty_stack_tt. + + CLASS-METHODS join_path + IMPORTING + it_stack TYPE ty_stack_tt + RETURNING + VALUE(rv_path) TYPE string. + + METHODS raise + IMPORTING + iv_error TYPE string + RAISING + zcx_abapgit_ajson_error. + +ENDCLASS. + +CLASS lcl_json_parser IMPLEMENTATION. + + METHOD parse. + + DATA lo_reader TYPE REF TO if_sxml_reader. + DATA lr_stack_top LIKE LINE OF mt_stack. + DATA lo_node TYPE REF TO if_sxml_node. + FIELD-SYMBOLS LIKE LINE OF rt_json_tree. + DATA lt_attributes TYPE if_sxml_attribute=>attributes. + DATA lo_attr LIKE LINE OF lt_attributes. + DATA lo_open TYPE REF TO if_sxml_open_element. + DATA lo_close TYPE REF TO if_sxml_close_element. + DATA lo_value TYPE REF TO if_sxml_value_node. + + CLEAR mt_stack. + lo_reader = cl_sxml_string_reader=>create( cl_abap_codepage=>convert_to( iv_json ) ). + + " TODO: self protection, check non-empty, check starting from object ... + + DO. + lo_node = lo_reader->read_next_node( ). + IF lo_node IS NOT BOUND. + EXIT. + ENDIF. + + + CASE lo_node->type. + WHEN if_sxml_node=>co_nt_element_open. + lo_open ?= lo_node. + + APPEND INITIAL LINE TO rt_json_tree ASSIGNING . + + -type = to_lower( lo_open->qname-name ). + + READ TABLE mt_stack INDEX 1 INTO lr_stack_top. + IF sy-subrc = 0. + -path = join_path( mt_stack ). + lr_stack_top->children = lr_stack_top->children + 1. + + IF lr_stack_top->type = 'array'. + -name = |{ lr_stack_top->children }|. + -index = lr_stack_top->children. + ELSE. + lt_attributes = lo_open->get_attributes( ). + LOOP AT lt_attributes INTO lo_attr. + IF lo_attr->qname-name = 'name' AND lo_attr->value_type = if_sxml_value=>co_vt_text. + -name = lo_attr->get_value( ). + ENDIF. + ENDLOOP. + ENDIF. + ENDIF. + + GET REFERENCE OF INTO lr_stack_top. + INSERT lr_stack_top INTO mt_stack INDEX 1. + + WHEN if_sxml_node=>co_nt_element_close. + lo_close ?= lo_node. + + READ TABLE mt_stack INDEX 1 INTO lr_stack_top. + DELETE mt_stack INDEX 1. + IF lo_close->qname-name <> lr_stack_top->type. + raise( 'Unexpected closing node type' ). + ENDIF. + + WHEN if_sxml_node=>co_nt_value. + lo_value ?= lo_node. + + -value = lo_value->get_value( ). + + WHEN OTHERS. + raise( 'Unexpected node type' ). + ENDCASE. + ENDDO. + + IF lines( mt_stack ) > 0. + raise( 'Unexpected end of data' ). + ENDIF. + + ENDMETHOD. + + METHOD join_path. + + FIELD-SYMBOLS LIKE LINE OF it_stack. + + LOOP AT it_stack ASSIGNING . + rv_path = ->name && '/' && rv_path. + ENDLOOP. + + ENDMETHOD. + + METHOD raise. + + zcx_abapgit_ajson_error=>raise_json( + iv_location = join_path( mt_stack ) + iv_msg = |JSON PARSER: { iv_error } @ { join_path( mt_stack ) }| ). + + ENDMETHOD. + +ENDCLASS. + + +********************************************************************** +* JSON_TO_ABAP +********************************************************************** + +CLASS lcl_json_to_abap DEFINITION FINAL. + PUBLIC SECTION. + + METHODS find_loc + IMPORTING + iv_path TYPE string + iv_name TYPE string OPTIONAL " not mandatory + iv_append_tables TYPE abap_bool DEFAULT abap_false + RETURNING + VALUE(rv_ref) TYPE REF TO data + RAISING + zcx_abapgit_ajson_error. + + CLASS-METHODS bind + CHANGING + cv_obj TYPE any + co_instance TYPE REF TO lcl_json_to_abap. + + METHODS to_abap + IMPORTING + it_nodes TYPE zcl_abapgit_ajson=>ty_nodes_ts + RAISING + zcx_abapgit_ajson_error. + + PRIVATE SECTION. + DATA mr_obj TYPE REF TO data. +ENDCLASS. + +CLASS lcl_json_to_abap IMPLEMENTATION. + + METHOD bind. + CREATE OBJECT co_instance. + GET REFERENCE OF cv_obj INTO co_instance->mr_obj. + ENDMETHOD. + + METHOD to_abap. + + DATA lv_ref TYPE REF TO data. + DATA lv_type TYPE c. + DATA lo_x TYPE REF TO cx_root. + DATA lv_y TYPE c LENGTH 4. + DATA lv_m TYPE c LENGTH 2. + DATA lv_d TYPE c LENGTH 2. + + FIELD-SYMBOLS LIKE LINE OF it_nodes. + FIELD-SYMBOLS TYPE any. + + TRY. + LOOP AT it_nodes ASSIGNING USING KEY array_index. + lv_ref = find_loc( + iv_append_tables = abap_true + iv_path = -path + iv_name = -name ). + ASSIGN lv_ref->* TO . + ASSERT sy-subrc = 0. + DESCRIBE FIELD TYPE lv_type. + + CASE -type. + WHEN 'null'. + " Do nothing + WHEN 'bool'. + = boolc( -value = 'true' ). + WHEN 'num'. + = -value. + WHEN 'str'. + IF lv_type = 'D' AND -value IS NOT INITIAL. + FIND FIRST OCCURRENCE OF REGEX '^(\d{4})-(\d{2})-(\d{2})(T|$)' + IN -value + SUBMATCHES lv_y lv_m lv_d. + IF sy-subrc <> 0. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Unexpected date format' + iv_location = -path && -name ). + ENDIF. + CONCATENATE lv_y lv_m lv_d INTO . + ELSE. + = -value. + ENDIF. + WHEN 'object'. + IF NOT lv_type CO 'uv'. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Expected structure' + iv_location = -path && -name ). + ENDIF. + WHEN 'array'. + IF NOT lv_type CO 'h'. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Expected table' + iv_location = -path && -name ). + ENDIF. + WHEN OTHERS. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = |Unexpected JSON type [{ -type }]| + iv_location = -path && -name ). + ENDCASE. + + ENDLOOP. + CATCH cx_sy_conversion_no_number INTO lo_x. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = |Source is not a number| + iv_location = -path && -name ). + ENDTRY. + + ENDMETHOD. + + METHOD find_loc. + + DATA lt_path TYPE string_table. + DATA lv_trace TYPE string. + DATA lv_type TYPE c. + DATA lv_size TYPE i. + DATA lv_index TYPE i. + FIELD-SYMBOLS TYPE any. + FIELD-SYMBOLS TYPE STANDARD TABLE. + FIELD-SYMBOLS TYPE any. + FIELD-SYMBOLS LIKE LINE OF lt_path. + + SPLIT iv_path AT '/' INTO TABLE lt_path. + DELETE lt_path WHERE table_line IS INITIAL. + IF iv_name IS NOT INITIAL. + APPEND iv_name TO lt_path. + ENDIF. + + rv_ref = mr_obj. + + LOOP AT lt_path ASSIGNING . + lv_trace = lv_trace && '/' && . + = to_upper( ). + + ASSIGN rv_ref->* TO . + ASSERT sy-subrc = 0. + DESCRIBE FIELD TYPE lv_type. + + IF lv_type CA 'lr'. " data/obj ref + " TODO maybe in future + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Cannot assign to ref' + iv_location = lv_trace ). + + ELSEIF lv_type = 'h'. " table + IF NOT CO '0123456789'. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Need index to access tables' + iv_location = lv_trace ). + ENDIF. + lv_index = . + ASSIGN rv_ref->* TO . + ASSERT sy-subrc = 0. + + lv_size = lines( ). + IF iv_append_tables = abap_true AND lv_index = lv_size + 1. + APPEND INITIAL LINE TO . + ENDIF. + + READ TABLE INDEX lv_index ASSIGNING . + IF sy-subrc <> 0. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Index not found in table' + iv_location = lv_trace ). + ENDIF. + + ELSEIF lv_type CA 'uv'. " structure + ASSIGN COMPONENT OF STRUCTURE TO . + IF sy-subrc <> 0. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Path not found' + iv_location = lv_trace ). + ENDIF. + ELSE. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'Target is not deep' + iv_location = lv_trace ). + ENDIF. + GET REFERENCE OF INTO rv_ref. + ENDLOOP. + + ENDMETHOD. + +ENDCLASS. diff --git a/src/json/zcl_abapgit_ajson.clas.testclasses.abap b/src/json/zcl_abapgit_ajson.clas.testclasses.abap new file mode 100644 index 000000000..78a318eb7 --- /dev/null +++ b/src/json/zcl_abapgit_ajson.clas.testclasses.abap @@ -0,0 +1,1191 @@ +********************************************************************** +* UTIL +********************************************************************** +CLASS lcl_nodes_helper DEFINITION FINAL. + PUBLIC SECTION. + + DATA mt_nodes TYPE zcl_abapgit_ajson=>ty_nodes_tt. + METHODS add + IMPORTING + iv_str TYPE string. + METHODS sorted + RETURNING + VALUE(rt_nodes) TYPE zcl_abapgit_ajson=>ty_nodes_ts. + +ENDCLASS. + +CLASS lcl_nodes_helper IMPLEMENTATION. + METHOD add. + + FIELD-SYMBOLS LIKE LINE OF mt_nodes. + DATA lv_children TYPE string. + DATA lv_index TYPE string. + + APPEND INITIAL LINE TO mt_nodes ASSIGNING . + + SPLIT iv_str AT '|' INTO + -path + -name + -type + -value + lv_index + lv_children. + CONDENSE -path. + CONDENSE -name. + CONDENSE -type. + CONDENSE -value. + -index = lv_index. + -children = lv_children. + + ENDMETHOD. + + METHOD sorted. + rt_nodes = mt_nodes. + ENDMETHOD. +ENDCLASS. + +********************************************************************** +* PARSER +********************************************************************** + +CLASS ltcl_parser_test DEFINITION FINAL + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PUBLIC SECTION. + + CLASS-METHODS sample_json + RETURNING + VALUE(rv_json) TYPE string. + + PRIVATE SECTION. + + METHODS parse FOR TESTING RAISING zcx_abapgit_ajson_error. + +ENDCLASS. + +CLASS ltcl_parser_test IMPLEMENTATION. + + METHOD sample_json. + + rv_json = + '{' && + ' "string": "abc",' && + ' "number": 123,' && + ' "float": 123.45,' && + ' "boolean": true,' && + ' "false": false,' && + ' "null": null,' && + ' "date": "2020-03-15",' && + ' "issues": [' && + ' {' && + ' "message": "Indentation problem ...",' && + ' "key": "indentation",' && + ' "start": {' && + ' "row": 4,' && + ' "col": 3' && + ' },' && + ' "end": {' && + ' "row": 4,' && + ' "col": 26' && + ' },' && + ' "filename": "./zxxx.prog.abap"' && + ' },' && + ' {' && + ' "message": "Remove space before XXX",' && + ' "key": "space_before_dot",' && + ' "start": {' && + ' "row": 3,' && + ' "col": 21' && + ' },' && + ' "end": {' && + ' "row": 3,' && + ' "col": 22' && + ' },' && + ' "filename": "./zxxx.prog.abap"' && + ' }' && + ' ]' && + '}'. + + ENDMETHOD. + + METHOD parse. + + DATA lo_cut TYPE REF TO lcl_json_parser. + DATA lt_act TYPE zcl_abapgit_ajson=>ty_nodes_tt. + DATA lo_nodes TYPE REF TO lcl_nodes_helper. + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |object | | |8' ). + lo_nodes->add( '/ |string |str |abc | |0' ). + lo_nodes->add( '/ |number |num |123 | |0' ). + lo_nodes->add( '/ |float |num |123.45 | |0' ). + lo_nodes->add( '/ |boolean |bool |true | |0' ). + lo_nodes->add( '/ |false |bool |false | |0' ). + lo_nodes->add( '/ |null |null | | |0' ). + lo_nodes->add( '/ |date |str |2020-03-15 | |0' ). + lo_nodes->add( '/ |issues |array | | |2' ). + lo_nodes->add( '/issues/ |1 |object | |1 |5' ). + lo_nodes->add( '/issues/1/ |message |str |Indentation problem ... | |0' ). + lo_nodes->add( '/issues/1/ |key |str |indentation | |0' ). + lo_nodes->add( '/issues/1/ |start |object | | |2' ). + lo_nodes->add( '/issues/1/start/ |row |num |4 | |0' ). + lo_nodes->add( '/issues/1/start/ |col |num |3 | |0' ). + lo_nodes->add( '/issues/1/ |end |object | | |2' ). + lo_nodes->add( '/issues/1/end/ |row |num |4 | |0' ). + lo_nodes->add( '/issues/1/end/ |col |num |26 | |0' ). + lo_nodes->add( '/issues/1/ |filename |str |./zxxx.prog.abap | |0' ). + lo_nodes->add( '/issues/ |2 |object | |2 |5' ). + lo_nodes->add( '/issues/2/ |message |str |Remove space before XXX | |0' ). + lo_nodes->add( '/issues/2/ |key |str |space_before_dot | |0' ). + lo_nodes->add( '/issues/2/ |start |object | | |2' ). + lo_nodes->add( '/issues/2/start/ |row |num |3 | |0' ). + lo_nodes->add( '/issues/2/start/ |col |num |21 | |0' ). + lo_nodes->add( '/issues/2/ |end |object | | |2' ). + lo_nodes->add( '/issues/2/end/ |row |num |3 | |0' ). + lo_nodes->add( '/issues/2/end/ |col |num |22 | |0' ). + lo_nodes->add( '/issues/2/ |filename |str |./zxxx.prog.abap | |0' ). + + CREATE OBJECT lo_cut. + lt_act = lo_cut->parse( sample_json( ) ). + cl_abap_unit_assert=>assert_equals( + act = lt_act + exp = lo_nodes->mt_nodes ). + + ENDMETHOD. + +ENDCLASS. + + +********************************************************************** +* UTILS +********************************************************************** + +CLASS ltcl_utils_test DEFINITION FINAL + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PRIVATE SECTION. + + METHODS normalize_path FOR TESTING. + METHODS split_path FOR TESTING. + +ENDCLASS. + +CLASS zcl_abapgit_ajson DEFINITION LOCAL FRIENDS ltcl_utils_test. + +CLASS ltcl_utils_test IMPLEMENTATION. + + METHOD normalize_path. + + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>normalize_path( '' ) + exp = '/' ). + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>normalize_path( '/' ) + exp = '/' ). + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>normalize_path( 'abc' ) + exp = '/abc/' ). + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>normalize_path( '/abc' ) + exp = '/abc/' ). + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>normalize_path( 'abc/' ) + exp = '/abc/' ). + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>normalize_path( '/abc/' ) + exp = '/abc/' ). + + ENDMETHOD. + + METHOD split_path. + + DATA ls_exp TYPE zcl_abapgit_ajson=>ty_path_name. + DATA lv_path TYPE string. + + lv_path = ''. " alias to root + ls_exp-path = ''. + ls_exp-name = ''. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = '/'. + ls_exp-path = ''. + ls_exp-name = ''. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = '/abc/'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = 'abc'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = '/abc'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = 'abc/'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = '/abc/xyz'. + ls_exp-path = '/abc/'. + ls_exp-name = 'xyz'. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = '/abc/xyz/'. + ls_exp-path = '/abc/'. + ls_exp-name = 'xyz'. + cl_abap_unit_assert=>assert_equals( + act = lcl_utils=>split_path( lv_path ) + exp = ls_exp ). + + ENDMETHOD. + +ENDCLASS. + +********************************************************************** +* READER +********************************************************************** + +CLASS ltcl_reader_test DEFINITION FINAL + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PRIVATE SECTION. + + METHODS get_value FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS exists FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS value_integer FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS value_number FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS value_boolean FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS value_string FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS members FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS slice FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS array_to_string_table FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS get_date FOR TESTING RAISING zcx_abapgit_ajson_error. + +ENDCLASS. + +CLASS zcl_abapgit_ajson DEFINITION LOCAL FRIENDS ltcl_reader_test. + +CLASS ltcl_reader_test IMPLEMENTATION. + + METHOD slice. + + DATA lo_cut TYPE REF TO zcl_abapgit_ajson. + DATA lo_nodes TYPE REF TO lcl_nodes_helper. + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |array | | |2' ). + lo_nodes->add( '/ |1 |object | |1 |5' ). + lo_nodes->add( '/1/ |message |str |Indentation problem ... | |0' ). + lo_nodes->add( '/1/ |key |str |indentation | |0' ). + lo_nodes->add( '/1/ |start |object | | |2' ). + lo_nodes->add( '/1/start/ |row |num |4 | |0' ). + lo_nodes->add( '/1/start/ |col |num |3 | |0' ). + lo_nodes->add( '/1/ |end |object | | |2' ). + lo_nodes->add( '/1/end/ |row |num |4 | |0' ). + lo_nodes->add( '/1/end/ |col |num |26 | |0' ). + lo_nodes->add( '/1/ |filename |str |./zxxx.prog.abap | |0' ). + lo_nodes->add( '/ |2 |object | |2 |5' ). + lo_nodes->add( '/2/ |message |str |Remove space before XXX | |0' ). + lo_nodes->add( '/2/ |key |str |space_before_dot | |0' ). + lo_nodes->add( '/2/ |start |object | | |2' ). + lo_nodes->add( '/2/start/ |row |num |3 | |0' ). + lo_nodes->add( '/2/start/ |col |num |21 | |0' ). + lo_nodes->add( '/2/ |end |object | | |2' ). + lo_nodes->add( '/2/end/ |row |num |3 | |0' ). + lo_nodes->add( '/2/end/ |col |num |22 | |0' ). + lo_nodes->add( '/2/ |filename |str |./zxxx.prog.abap | |0' ). + + + lo_cut = zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + lo_cut ?= lo_cut->zif_abapgit_ajson_reader~slice( '/issues' ). + cl_abap_unit_assert=>assert_equals( + act = lo_cut->mt_json_tree + exp = lo_nodes->sorted( ) ). + + " ********************************************************************** + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |object | | |8' ). + lo_nodes->add( '/ |string |str |abc | |0' ). + lo_nodes->add( '/ |number |num |123 | |0' ). + lo_nodes->add( '/ |float |num |123.45 | |0' ). + lo_nodes->add( '/ |boolean |bool |true | |0' ). + lo_nodes->add( '/ |false |bool |false | |0' ). + lo_nodes->add( '/ |null |null | | |0' ). + lo_nodes->add( '/ |date |str |2020-03-15 | |0' ). + lo_nodes->add( '/ |issues |array | | |2' ). + lo_nodes->add( '/issues/ |1 |object | |1 |5' ). + lo_nodes->add( '/issues/1/ |message |str |Indentation problem ... | |0' ). + lo_nodes->add( '/issues/1/ |key |str |indentation | |0' ). + lo_nodes->add( '/issues/1/ |start |object | | |2' ). + lo_nodes->add( '/issues/1/start/ |row |num |4 | |0' ). + lo_nodes->add( '/issues/1/start/ |col |num |3 | |0' ). + lo_nodes->add( '/issues/1/ |end |object | | |2' ). + lo_nodes->add( '/issues/1/end/ |row |num |4 | |0' ). + lo_nodes->add( '/issues/1/end/ |col |num |26 | |0' ). + lo_nodes->add( '/issues/1/ |filename |str |./zxxx.prog.abap | |0' ). + lo_nodes->add( '/issues/ |2 |object | |2 |5' ). + lo_nodes->add( '/issues/2/ |message |str |Remove space before XXX | |0' ). + lo_nodes->add( '/issues/2/ |key |str |space_before_dot | |0' ). + lo_nodes->add( '/issues/2/ |start |object | | |2' ). + lo_nodes->add( '/issues/2/start/ |row |num |3 | |0' ). + lo_nodes->add( '/issues/2/start/ |col |num |21 | |0' ). + lo_nodes->add( '/issues/2/ |end |object | | |2' ). + lo_nodes->add( '/issues/2/end/ |row |num |3 | |0' ). + lo_nodes->add( '/issues/2/end/ |col |num |22 | |0' ). + lo_nodes->add( '/issues/2/ |filename |str |./zxxx.prog.abap | |0' ). + + lo_cut = zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + lo_cut ?= lo_cut->zif_abapgit_ajson_reader~slice( '/' ). + cl_abap_unit_assert=>assert_equals( + act = lo_cut->mt_json_tree + exp = lo_nodes->sorted( ) ). + + " ********************************************************************** + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |object | | |2' ). + lo_nodes->add( '/ |row |num |3 | |0' ). + lo_nodes->add( '/ |col |num |21 | |0' ). + + lo_cut = zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + lo_cut ?= lo_cut->zif_abapgit_ajson_reader~slice( '/issues/2/start/' ). + cl_abap_unit_assert=>assert_equals( + act = lo_cut->mt_json_tree + exp = lo_nodes->sorted( ) ). + + ENDMETHOD. + + METHOD get_value. + + DATA lo_cut TYPE REF TO zif_abapgit_ajson_reader. + lo_cut ?= zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get( '/string' ) + exp = 'abc' ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get( '/string/' ) + exp = 'abc' ). " Hmmm ? + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get( '/boolean' ) + exp = 'true' ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get( '/issues/2/start/row' ) + exp = '3' ). + + ENDMETHOD. + + METHOD exists. + + DATA lo_cut TYPE REF TO zif_abapgit_ajson_reader. + lo_cut ?= zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->exists( '/string' ) + exp = abap_true ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->exists( '/string/' ) + exp = abap_true ). " mmmm ? + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->exists( '/xxx' ) + exp = abap_false ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->exists( '/issues/2/start/row' ) + exp = abap_true ). + + ENDMETHOD. + + METHOD value_integer. + + DATA lo_cut TYPE REF TO zif_abapgit_ajson_reader. + lo_cut ?= zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_integer( '/string' ) + exp = 0 ). " Hmmmm ???? + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_integer( '/number' ) + exp = 123 ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_integer( '/float' ) + exp = 123 ). + + ENDMETHOD. + + METHOD value_number. + + DATA lo_cut TYPE REF TO zif_abapgit_ajson_reader. + lo_cut ?= zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_number( '/string' ) + exp = 0 ). " Hmmmm ???? + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_number( '/number' ) + exp = +'123.0' ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_number( '/float' ) + exp = +'123.45' ). + + ENDMETHOD. + + METHOD value_boolean. + + DATA lo_cut TYPE REF TO zif_abapgit_ajson_reader. + lo_cut ?= zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_boolean( '/string' ) + exp = abap_true ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_boolean( '/number' ) + exp = abap_true ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_boolean( '/xxx' ) + exp = abap_false ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_boolean( '/boolean' ) + exp = abap_true ). + + ENDMETHOD. + + METHOD value_string. + + DATA lo_cut TYPE REF TO zif_abapgit_ajson_reader. + lo_cut ?= zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_string( '/string' ) + exp = 'abc' ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_string( '/number' ) + exp = '123' ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_string( '/xxx' ) + exp = '' ). + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->get_string( '/boolean' ) + exp = 'true' ). + + ENDMETHOD. + + METHOD members. + + DATA lt_exp TYPE string_table. + DATA lo_cut TYPE REF TO zif_abapgit_ajson_reader. + lo_cut ?= zcl_abapgit_ajson=>parse( ltcl_parser_test=>sample_json( ) ). + + CLEAR lt_exp. + APPEND '1' TO lt_exp. + APPEND '2' TO lt_exp. + cl_abap_unit_assert=>assert_equals( + act = lo_cut->members( '/issues' ) + exp = lt_exp ). + + CLEAR lt_exp. + APPEND 'col' TO lt_exp. + APPEND 'row' TO lt_exp. + cl_abap_unit_assert=>assert_equals( + act = lo_cut->members( '/issues/1/start/' ) + exp = lt_exp ). + + ENDMETHOD. + + METHOD array_to_string_table. + + DATA lo_cut TYPE REF TO zcl_abapgit_ajson. + DATA lo_nodes TYPE REF TO lcl_nodes_helper. + DATA lt_act TYPE string_table. + DATA lt_exp TYPE string_table. + DATA lo_err TYPE REF TO zcx_abapgit_ajson_error. + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |array | | |6' ). + lo_nodes->add( '/ |1 |num |123 |1|0' ). + lo_nodes->add( '/ |2 |num |234 |2|0' ). + lo_nodes->add( '/ |3 |str |abc |3|0' ). + lo_nodes->add( '/ |4 |bool |true |4|0' ). + lo_nodes->add( '/ |5 |bool |false |5|0' ). + lo_nodes->add( '/ |6 |null |null |6|0' ). + + APPEND '123' TO lt_exp. + APPEND '234' TO lt_exp. + APPEND 'abc' TO lt_exp. + APPEND 'X' TO lt_exp. + APPEND '' TO lt_exp. + APPEND '' TO lt_exp. + + CREATE OBJECT lo_cut. + lo_cut->mt_json_tree = lo_nodes->mt_nodes. + + lt_act = lo_cut->zif_abapgit_ajson_reader~array_to_string_table( '/' ). + cl_abap_unit_assert=>assert_equals( + act = lt_act + exp = lt_exp ). + + " negative + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |object | | |1' ). + lo_nodes->add( '/ |a |str |abc | |0' ). + lo_cut->mt_json_tree = lo_nodes->mt_nodes. + + TRY. + lo_cut->zif_abapgit_ajson_reader~array_to_string_table( '/x' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Path not found: /x' ). + ENDTRY. + + TRY. + lo_cut->zif_abapgit_ajson_reader~array_to_string_table( '/' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Array expected at: /' ). + ENDTRY. + + TRY. + lo_cut->zif_abapgit_ajson_reader~array_to_string_table( '/a' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Array expected at: /a' ). + ENDTRY. + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |array | | |1' ). + lo_nodes->add( '/ |1 |object | |1|0' ). + lo_cut->mt_json_tree = lo_nodes->mt_nodes. + + TRY. + lo_cut->zif_abapgit_ajson_reader~array_to_string_table( '/' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Cannot convert [object] to string at [/1]' ). + ENDTRY. + + ENDMETHOD. + + METHOD get_date. + + DATA lo_cut TYPE REF TO zcl_abapgit_ajson. + DATA lo_nodes TYPE REF TO lcl_nodes_helper. + DATA lv_exp TYPE d. + + CREATE OBJECT lo_cut. + lv_exp = '20200728'. + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |object | | |1' ). + lo_nodes->add( '/ |date1 |str |2020-07-28 | |0' ). + lo_cut->mt_json_tree = lo_nodes->mt_nodes. + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->zif_abapgit_ajson_reader~get_date( '/date1' ) + exp = lv_exp ). + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |object | | |1' ). + lo_nodes->add( '/ |date1 |str |2020-07-28T01:00:00Z | |0' ). + lo_cut->mt_json_tree = lo_nodes->mt_nodes. + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->zif_abapgit_ajson_reader~get_date( '/date1' ) + exp = lv_exp ). + + CREATE OBJECT lo_nodes. + lo_nodes->add( ' | |object | | |1' ). + lo_nodes->add( '/ |date1 |str |20200728 | |0' ). + lo_cut->mt_json_tree = lo_nodes->mt_nodes. + + cl_abap_unit_assert=>assert_equals( + act = lo_cut->zif_abapgit_ajson_reader~get_date( '/date1' ) + exp = '' ). + + ENDMETHOD. + +ENDCLASS. + + +********************************************************************** +* JSON TO ABAP +********************************************************************** + +CLASS ltcl_json_to_abap DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT + FINAL. + + PRIVATE SECTION. + + TYPES: + BEGIN OF ty_struc, + a TYPE string, + b TYPE i, + END OF ty_struc, + tty_struc TYPE STANDARD TABLE OF ty_struc WITH DEFAULT KEY, + BEGIN OF ty_complex, + str TYPE string, + int TYPE i, + float TYPE f, + bool TYPE abap_bool, + obj TYPE ty_struc, + tab TYPE tty_struc, + oref TYPE REF TO object, + date1 TYPE d, + date2 TYPE d, + END OF ty_complex. + + METHODS find_loc FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS find_loc_negative FOR TESTING. + METHODS find_loc_append FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS to_abap FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS to_abap_negative FOR TESTING. + + METHODS prepare_cut + EXPORTING + eo_cut TYPE REF TO lcl_json_to_abap + es_elem TYPE ty_struc + es_mock TYPE ty_complex. + +ENDCLASS. + +CLASS ltcl_json_to_abap IMPLEMENTATION. + + METHOD prepare_cut. + + es_mock-str = 'Hello'. + es_mock-int = 10. + es_mock-obj-a = 'World'. + es_elem-a = 'One'. + es_elem-b = 1. + APPEND es_elem TO es_mock-tab. + es_elem-a = 'two'. + es_elem-b = 2. + APPEND es_elem TO es_mock-tab. + + lcl_json_to_abap=>bind( + CHANGING + cv_obj = es_mock + co_instance = eo_cut ). + + ENDMETHOD. + + METHOD find_loc. + + DATA ls_last_elem TYPE ty_struc. + DATA ls_mock TYPE ty_complex. + DATA lo_cut TYPE REF TO lcl_json_to_abap. + DATA lv_ref TYPE REF TO data. + FIELD-SYMBOLS TYPE any. + + prepare_cut( + IMPORTING + eo_cut = lo_cut + es_mock = ls_mock + es_elem = ls_last_elem ). + + + lv_ref = lo_cut->find_loc( 'str' ). " Relative also works but from root + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = 'Hello' ). + + lv_ref = lo_cut->find_loc( '/str' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = 'Hello' ). + + lv_ref = lo_cut->find_loc( '/int' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = 10 ). + + lv_ref = lo_cut->find_loc( '/obj/a' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = 'World' ). + + lv_ref = lo_cut->find_loc( + iv_path = '/obj' + iv_name = 'a' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = 'World' ). + + lv_ref = lo_cut->find_loc( '/obj' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = ls_mock-obj ). + + lv_ref = lo_cut->find_loc( '/' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = ls_mock ). + + lv_ref = lo_cut->find_loc( '/tab/2' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = ls_last_elem ). + + lv_ref = lo_cut->find_loc( '/tab/1/a' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = 'One' ). + + ENDMETHOD. + + METHOD find_loc_append. + + DATA ls_last_elem TYPE ty_struc. + DATA ls_mock TYPE ty_complex. + DATA lo_cut TYPE REF TO lcl_json_to_abap. + DATA lo_err TYPE REF TO zcx_abapgit_ajson_error. + DATA lv_ref TYPE REF TO data. + FIELD-SYMBOLS TYPE any. + + prepare_cut( + IMPORTING + eo_cut = lo_cut + es_mock = ls_mock + es_elem = ls_last_elem ). + + + lv_ref = lo_cut->find_loc( '/tab/1/a' ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = 'One' ). + + cl_abap_unit_assert=>assert_equals( + act = lines( ls_mock-tab ) + exp = 2 ). + + TRY. + lo_cut->find_loc( '/tab/3/a' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Index not found in table' ). + ENDTRY. + + lv_ref = lo_cut->find_loc( + iv_path = '/tab/3/a' + iv_append_tables = abap_true ). + ASSIGN lv_ref->* TO . + cl_abap_unit_assert=>assert_equals( + act = + exp = '' ). + cl_abap_unit_assert=>assert_equals( + act = lines( ls_mock-tab ) + exp = 3 ). + + TRY. + lo_cut->find_loc( '/tab/5/a' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Index not found in table' ). + ENDTRY. + + ENDMETHOD. + + METHOD find_loc_negative. + + DATA lo_cut TYPE REF TO lcl_json_to_abap. + DATA lo_err TYPE REF TO zcx_abapgit_ajson_error. + DATA ls_mock TYPE ty_complex. + + prepare_cut( + IMPORTING + es_mock = ls_mock " Must be here to keep reference alive + eo_cut = lo_cut ). + + TRY. + lo_cut->find_loc( '/xyz' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Path not found' ). + ENDTRY. + + TRY. + lo_cut->find_loc( '/oref/xyz' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Cannot assign to ref' ). + ENDTRY. + + TRY. + lo_cut->find_loc( '/tab/xyz' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Need index to access tables' ). + ENDTRY. + + TRY. + lo_cut->find_loc( '/tab/5' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Index not found in table' ). + ENDTRY. + + ENDMETHOD. + + METHOD to_abap. + + DATA lo_cut TYPE REF TO lcl_json_to_abap. + DATA ls_mock TYPE ty_complex. + DATA lv_exp_date TYPE d VALUE '20200728'. + DATA ls_elem LIKE LINE OF ls_mock-tab. + DATA lo_nodes TYPE REF TO lcl_nodes_helper. + lcl_json_to_abap=>bind( + CHANGING + cv_obj = ls_mock + co_instance = lo_cut ). + + CREATE OBJECT lo_nodes. + lo_nodes->add( '/ | |object | | ' ). + lo_nodes->add( '/ |str |str |hello | ' ). + lo_nodes->add( '/ |int |num |5 | ' ). + lo_nodes->add( '/ |float |num |5.5 | ' ). + lo_nodes->add( '/ |bool |bool |true | ' ). + lo_nodes->add( '/ |obj |object | | ' ). + lo_nodes->add( '/obj |a |str |world | ' ). + lo_nodes->add( '/ |tab |array | | ' ). + lo_nodes->add( '/tab |1 |object | |1' ). + lo_nodes->add( '/tab/1 |a |str | One | ' ). + lo_nodes->add( '/tab |2 |object | |2' ). + lo_nodes->add( '/tab/2 |a |str | Two | ' ). + lo_nodes->add( '/ |date1 |str |2020-07-28 | ' ). + lo_nodes->add( '/ |date2 |str |2020-07-28T00:00:00Z | ' ). + + lo_cut->to_abap( lo_nodes->sorted( ) ). + + cl_abap_unit_assert=>assert_equals( + act = ls_mock-str + exp = 'hello' ). + cl_abap_unit_assert=>assert_equals( + act = ls_mock-int + exp = 5 ). + cl_abap_unit_assert=>assert_equals( + act = ls_mock-float + exp = '5.5' ). + cl_abap_unit_assert=>assert_equals( + act = ls_mock-bool + exp = abap_true ). + cl_abap_unit_assert=>assert_equals( + act = ls_mock-obj-a + exp = 'world' ). + cl_abap_unit_assert=>assert_equals( + act = ls_mock-date1 + exp = lv_exp_date ). + cl_abap_unit_assert=>assert_equals( + act = ls_mock-date2 + exp = lv_exp_date ). + + cl_abap_unit_assert=>assert_equals( + act = lines( ls_mock-tab ) + exp = 2 ). + + READ TABLE ls_mock-tab INTO ls_elem INDEX 1. + cl_abap_unit_assert=>assert_equals( + act = ls_elem-a + exp = 'One' ). + READ TABLE ls_mock-tab INTO ls_elem INDEX 2. + cl_abap_unit_assert=>assert_equals( + act = ls_elem-a + exp = 'Two' ). + + ENDMETHOD. + + METHOD to_abap_negative. + + DATA lo_cut TYPE REF TO lcl_json_to_abap. + DATA lo_err TYPE REF TO zcx_abapgit_ajson_error. + DATA ls_mock TYPE ty_complex. + DATA lo_nodes TYPE REF TO lcl_nodes_helper. + + lcl_json_to_abap=>bind( + CHANGING + cv_obj = ls_mock + co_instance = lo_cut ). + + TRY. + CREATE OBJECT lo_nodes. + lo_nodes->add( '/ | |object | ' ). + lo_nodes->add( '/ |str |object | ' ). + + lo_cut->to_abap( lo_nodes->sorted( ) ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Expected structure' ). + ENDTRY. + + TRY. + CREATE OBJECT lo_nodes. + lo_nodes->add( '/ | |object | ' ). + lo_nodes->add( '/ |str |array | ' ). + + lo_cut->to_abap( lo_nodes->sorted( ) ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Expected table' ). + ENDTRY. + + TRY. + CREATE OBJECT lo_nodes. + lo_nodes->add( '/ | |object | ' ). + lo_nodes->add( '/ |int |str |hello ' ). + + lo_cut->to_abap( lo_nodes->sorted( ) ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Source is not a number' ). + ENDTRY. + + TRY. + CREATE OBJECT lo_nodes. + lo_nodes->add( '/ | |object | ' ). + lo_nodes->add( '/ |date1 |str |baddate ' ). + + lo_cut->to_abap( lo_nodes->sorted( ) ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_err. + cl_abap_unit_assert=>assert_equals( + act = lo_err->message + exp = 'Unexpected date format' ). + ENDTRY. + + ENDMETHOD. + +ENDCLASS. + + + +********************************************************************** +* INTEGRATED +********************************************************************** +CLASS ltcl_integrated DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT + FINAL. + + PRIVATE SECTION. + + TYPES: + BEGIN OF ty_loc, + row TYPE i, + col TYPE i, + END OF ty_loc, + BEGIN OF ty_issue, + message TYPE string, + key TYPE string, + filename TYPE string, + start TYPE ty_loc, + end TYPE ty_loc, + END OF ty_issue, + tt_issues TYPE STANDARD TABLE OF ty_issue WITH DEFAULT KEY, + BEGIN OF ty_target, + string TYPE string, + number TYPE i, + float TYPE f, + boolean TYPE abap_bool, + false TYPE abap_bool, + null TYPE string, + date TYPE string, " ??? TODO + issues TYPE tt_issues, + END OF ty_target. + + METHODS reader FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS array_index FOR TESTING RAISING zcx_abapgit_ajson_error. + METHODS array_simple FOR TESTING RAISING zcx_abapgit_ajson_error. + +ENDCLASS. + +CLASS ltcl_integrated IMPLEMENTATION. + + METHOD array_simple. + + DATA lt_act TYPE string_table. + DATA lt_exp TYPE string_table. + DATA lv_exp TYPE string. + DATA li_reader TYPE REF TO zif_abapgit_ajson_reader. + DATA lv_src TYPE string. + + lv_src = '['. + DO 10 TIMES. + IF sy-index <> 1. + lv_src = lv_src && `, `. + ENDIF. + lv_src = lv_src && |"{ sy-index }"|. + lv_exp = |{ sy-index }|. + APPEND lv_exp TO lt_exp. + ENDDO. + lv_src = lv_src && ']'. + + li_reader = zcl_abapgit_ajson=>parse( lv_src ). + li_reader->to_abap( IMPORTING ev_container = lt_act ). + + cl_abap_unit_assert=>assert_equals( + act = lt_act + exp = lt_exp ). + + ENDMETHOD. + + METHOD array_index. + + DATA lt_act TYPE TABLE OF ty_loc. + DATA lt_exp TYPE TABLE OF ty_loc. + DATA ls_exp TYPE ty_loc. + DATA li_reader TYPE REF TO zif_abapgit_ajson_reader. + DATA lv_src TYPE string. + + lv_src = '['. + DO 10 TIMES. + IF sy-index <> 1. + lv_src = lv_src && `, `. + ENDIF. + lv_src = lv_src && |\{ "row": { sy-index } \}|. + ls_exp-row = sy-index. + APPEND ls_exp TO lt_exp. + ENDDO. + lv_src = lv_src && ']'. + + li_reader = zcl_abapgit_ajson=>parse( lv_src ). + li_reader->to_abap( IMPORTING ev_container = lt_act ). + + cl_abap_unit_assert=>assert_equals( + act = lt_act + exp = lt_exp ). + + ENDMETHOD. + + METHOD reader. + + DATA lv_source TYPE string. + DATA li_reader TYPE REF TO zif_abapgit_ajson_reader. + DATA ls_act TYPE ty_target. + DATA ls_exp TYPE ty_target. + FIELD-SYMBOLS LIKE LINE OF ls_exp-issues. + + lv_source = ltcl_parser_test=>sample_json( ). + li_reader = zcl_abapgit_ajson=>parse( lv_source ). + + cl_abap_unit_assert=>assert_equals( + act = li_reader->get( '/string' ) + exp = 'abc' ). + + + ls_exp-string = 'abc'. + ls_exp-number = 123. + ls_exp-float = '123.45'. + ls_exp-boolean = abap_true. + ls_exp-false = abap_false. + ls_exp-date = '2020-03-15'. + + APPEND INITIAL LINE TO ls_exp-issues ASSIGNING . + -message = 'Indentation problem ...'. + -key = 'indentation'. + -filename = './zxxx.prog.abap'. + -start-row = 4. + -start-col = 3. + -end-row = 4. + -end-col = 26. + + APPEND INITIAL LINE TO ls_exp-issues ASSIGNING . + -message = 'Remove space before XXX'. + -key = 'space_before_dot'. + -filename = './zxxx.prog.abap'. + -start-row = 3. + -start-col = 21. + -end-row = 3. + -end-col = 22. + + li_reader->to_abap( IMPORTING ev_container = ls_act ). + + cl_abap_unit_assert=>assert_equals( + act = ls_act + exp = ls_exp ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/json/zcl_abapgit_ajson.clas.xml b/src/json/zcl_abapgit_ajson.clas.xml new file mode 100644 index 000000000..0812a16e0 --- /dev/null +++ b/src/json/zcl_abapgit_ajson.clas.xml @@ -0,0 +1,17 @@ + + + + + + ZCL_ABAPGIT_AJSON + E + AJSON reader + 1 + X + X + X + X + + + + diff --git a/src/json/zcx_abapgit_ajson_error.clas.abap b/src/json/zcx_abapgit_ajson_error.clas.abap new file mode 100644 index 000000000..d7486e8e6 --- /dev/null +++ b/src/json/zcx_abapgit_ajson_error.clas.abap @@ -0,0 +1,91 @@ +class ZCX_ABAPGIT_AJSON_ERROR definition + public + inheriting from ZCX_ABAPGIT_EXCEPTION + final + create public . + +public section. + + constants: + begin of ZCX_ABAPGIT_AJSON_ERROR, + msgid type symsgid value '00', + msgno type symsgno value '001', + attr1 type scx_attrname value 'MSGV1', + attr2 type scx_attrname value 'MSGV2', + attr3 type scx_attrname value 'MSGV3', + attr4 type scx_attrname value 'MSGV4', + end of ZCX_ABAPGIT_AJSON_ERROR . + data MESSAGE type STRING read-only . + + methods CONSTRUCTOR + importing + !TEXTID like IF_T100_MESSAGE=>T100KEY optional + !PREVIOUS like PREVIOUS optional + !MSGV1 type SYMSGV optional + !MSGV2 type SYMSGV optional + !MSGV3 type SYMSGV optional + !MSGV4 type SYMSGV optional + !MESSAGE type STRING optional . + class-methods RAISE_JSON + importing + !IV_MSG type STRING + !IV_LOCATION type STRING optional + raising + ZCX_ABAPGIT_AJSON_ERROR . +protected section. +private section. +ENDCLASS. + + + +CLASS ZCX_ABAPGIT_AJSON_ERROR IMPLEMENTATION. + + + method CONSTRUCTOR. +CALL METHOD SUPER->CONSTRUCTOR +EXPORTING +PREVIOUS = PREVIOUS +MSGV1 = MSGV1 +MSGV2 = MSGV2 +MSGV3 = MSGV3 +MSGV4 = MSGV4 +. +me->MESSAGE = MESSAGE . +clear me->textid. +if textid is initial. + IF_T100_MESSAGE~T100KEY = ZCX_ABAPGIT_AJSON_ERROR . +else. + IF_T100_MESSAGE~T100KEY = TEXTID. +endif. + endmethod. + + + METHOD raise_json. + + DATA lv_tmp TYPE string. + DATA: + BEGIN OF ls_msg, + a1 LIKE msgv1, + a2 LIKE msgv1, + a3 LIKE msgv1, + a4 LIKE msgv1, + END OF ls_msg. + + IF iv_location IS INITIAL. + ls_msg = iv_msg. + ELSE. + lv_tmp = iv_msg && | @{ iv_location }|. + ls_msg = lv_tmp. + ENDIF. + + RAISE EXCEPTION TYPE zcx_abapgit_ajson_error + EXPORTING + textid = zcx_abapgit_ajson_error + message = iv_msg + msgv1 = ls_msg-a1 + msgv2 = ls_msg-a2 + msgv3 = ls_msg-a3 + msgv4 = ls_msg-a4. + + ENDMETHOD. +ENDCLASS. diff --git a/src/json/zcx_abapgit_ajson_error.clas.testclasses.abap b/src/json/zcx_abapgit_ajson_error.clas.testclasses.abap new file mode 100644 index 000000000..ed2ad7a51 --- /dev/null +++ b/src/json/zcx_abapgit_ajson_error.clas.testclasses.abap @@ -0,0 +1,55 @@ +CLASS ltcl_error DEFINITION + FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT + FINAL. + + PRIVATE SECTION. + + METHODS raise FOR TESTING. + METHODS raise_w_location FOR TESTING. + +ENDCLASS. + +CLASS ltcl_error IMPLEMENTATION. + + METHOD raise. + + DATA lo_x TYPE REF TO zcx_abapgit_ajson_error. + DATA lv_msg TYPE string. + + lv_msg = repeat( + val = 'a' + occ = 50 ) && repeat( + val = 'b' + occ = 50 ) && '123'. + + TRY. + zcx_abapgit_ajson_error=>raise_json( lv_msg ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_x. + cl_abap_unit_assert=>assert_equals( + exp = lv_msg + act = lo_x->get_text( ) ). + ENDTRY. + + ENDMETHOD. + + METHOD raise_w_location. + + DATA lo_x TYPE REF TO zcx_abapgit_ajson_error. + + TRY. + zcx_abapgit_ajson_error=>raise_json( + iv_msg = 'a' + iv_location = 'b' ). + cl_abap_unit_assert=>fail( ). + CATCH zcx_abapgit_ajson_error INTO lo_x. + cl_abap_unit_assert=>assert_equals( + exp = 'a @b' + act = lo_x->get_text( ) ). + ENDTRY. + + ENDMETHOD. + +ENDCLASS. diff --git a/src/json/zcx_abapgit_ajson_error.clas.xml b/src/json/zcx_abapgit_ajson_error.clas.xml new file mode 100644 index 000000000..5f4e98e0f --- /dev/null +++ b/src/json/zcx_abapgit_ajson_error.clas.xml @@ -0,0 +1,19 @@ + + + + + + ZCX_ABAPGIT_AJSON_ERROR + E + AJSON exception + 40 + 1 + X + X + X + 00 + X + + + + diff --git a/src/json/zif_abapgit_ajson_reader.intf.abap b/src/json/zif_abapgit_ajson_reader.intf.abap new file mode 100644 index 000000000..64174f64e --- /dev/null +++ b/src/json/zif_abapgit_ajson_reader.intf.abap @@ -0,0 +1,62 @@ +INTERFACE zif_abapgit_ajson_reader + PUBLIC . + + METHODS exists + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_exists) TYPE abap_bool. + METHODS members + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rt_members) TYPE string_table. + METHODS get + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_value) TYPE string. + METHODS get_boolean + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_value) TYPE abap_bool. + METHODS get_integer + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_value) TYPE i. + METHODS get_number + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_value) TYPE f. + METHODS get_date + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_value) TYPE d. + METHODS get_string + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rv_value) TYPE string. + METHODS slice + IMPORTING + iv_path TYPE string + RETURNING + VALUE(ri_json) TYPE REF TO zif_abapgit_ajson_reader. + METHODS to_abap + EXPORTING + ev_container TYPE any + RAISING + zcx_abapgit_ajson_error. + METHODS array_to_string_table + IMPORTING + iv_path TYPE string + RETURNING + VALUE(rt_string_table) TYPE string_table + RAISING + zcx_abapgit_ajson_error. + +ENDINTERFACE. diff --git a/src/json/zif_abapgit_ajson_reader.intf.xml b/src/json/zif_abapgit_ajson_reader.intf.xml new file mode 100644 index 000000000..69952631d --- /dev/null +++ b/src/json/zif_abapgit_ajson_reader.intf.xml @@ -0,0 +1,15 @@ + + + + + + ZIF_ABAPGIT_AJSON_READER + E + AJSON reader interface + 2 + 1 + X + + + + diff --git a/src/persist/zif_abapgit_persistence.intf.abap b/src/persist/zif_abapgit_persistence.intf.abap index 53039e9c7..c4e4adee9 100644 --- a/src/persist/zif_abapgit_persistence.intf.abap +++ b/src/persist/zif_abapgit_persistence.intf.abap @@ -40,6 +40,7 @@ INTERFACE zif_abapgit_persistence PUBLIC. deserialized_by TYPE xubname, deserialized_at TYPE timestampl, offline TYPE abap_bool, + switched_origin TYPE string, local_checksums TYPE ty_local_checksum_tt, dot_abapgit TYPE zif_abapgit_dot_abapgit=>ty_dot_abapgit, head_branch TYPE string, " HEAD symref of the repo, master branch @@ -56,6 +57,7 @@ INTERFACE zif_abapgit_persistence PUBLIC. deserialized_by TYPE abap_bool, deserialized_at TYPE abap_bool, offline TYPE abap_bool, + switched_origin TYPE abap_bool, local_checksums TYPE abap_bool, dot_abapgit TYPE abap_bool, head_branch TYPE abap_bool, diff --git a/src/ui/zcl_abapgit_gui_page_repo_sett.clas.abap b/src/ui/zcl_abapgit_gui_page_repo_sett.clas.abap index 49d6ba222..5cb46d6f5 100644 --- a/src/ui/zcl_abapgit_gui_page_repo_sett.clas.abap +++ b/src/ui/zcl_abapgit_gui_page_repo_sett.clas.abap @@ -29,6 +29,11 @@ CLASS zcl_abapgit_gui_page_repo_sett DEFINITION !ii_html TYPE REF TO zif_abapgit_html RAISING zcx_abapgit_exception . + METHODS render_remotes + IMPORTING + !ii_html TYPE REF TO zif_abapgit_html + RAISING + zcx_abapgit_exception . METHODS save IMPORTING !it_postdata TYPE cnht_post_data_tab @@ -44,6 +49,11 @@ CLASS zcl_abapgit_gui_page_repo_sett DEFINITION !it_post_fields TYPE tihttpnvp RAISING zcx_abapgit_exception . + METHODS save_remotes + IMPORTING + !it_post_fields TYPE tihttpnvp + RAISING + zcx_abapgit_exception . METHODS parse_post IMPORTING !it_postdata TYPE cnht_post_data_tab @@ -102,6 +112,9 @@ CLASS zcl_abapgit_gui_page_repo_sett IMPLEMENTATION. ri_html->add( |
| ). render_dot_abapgit( ri_html ). + IF mo_repo->is_offline( ) = abap_false. + render_remotes( ri_html ). + ENDIF. render_local_settings( ri_html ). ri_html->add( '' ). @@ -300,6 +313,30 @@ CLASS zcl_abapgit_gui_page_repo_sett IMPLEMENTATION. ENDMETHOD. + METHOD render_remotes. + + DATA lo_repo_online TYPE REF TO zcl_abapgit_repo_online. + + lo_repo_online ?= mo_repo. + + ii_html->add( '

Remotes

' ). + ii_html->add( '' ). + + " TODO maybe make it editable ? + ii_html->add( render_table_row( + iv_name = 'Current remote' + iv_value = |{ lo_repo_online->get_url( ) + } @{ lo_repo_online->get_branch_name( ) }| ) ). + ii_html->add( render_table_row( + iv_name = 'Switched origin' + iv_value = || ) ). + + ii_html->add( '
' ). + + ENDMETHOD. + + METHOD render_table_row. rv_html = '' @@ -318,6 +355,7 @@ CLASS zcl_abapgit_gui_page_repo_sett IMPLEMENTATION. lt_post_fields = parse_post( it_postdata ). save_dot_abap( lt_post_fields ). + save_remotes( lt_post_fields ). save_local_settings( lt_post_fields ). mo_repo->refresh( ). @@ -432,6 +470,26 @@ CLASS zcl_abapgit_gui_page_repo_sett IMPLEMENTATION. ENDMETHOD. + METHOD save_remotes. + + DATA ls_post_field LIKE LINE OF it_post_fields. + DATA lo_online_repo TYPE REF TO zcl_abapgit_repo_online. + + IF mo_repo->is_offline( ) = abap_true. + RETURN. + ENDIF. + + lo_online_repo ?= mo_repo. + + READ TABLE it_post_fields INTO ls_post_field WITH KEY name = 'switched_origin'. + ASSERT sy-subrc = 0. + lo_online_repo->switch_origin( + iv_url = ls_post_field-value + iv_overwrite = abap_true ). + + ENDMETHOD. + + METHOD zif_abapgit_gui_event_handler~on_event. CASE iv_action. diff --git a/src/ui/zcl_abapgit_gui_page_view_repo.clas.abap b/src/ui/zcl_abapgit_gui_page_view_repo.clas.abap index b5a45abfa..0f63ce946 100644 --- a/src/ui/zcl_abapgit_gui_page_view_repo.clas.abap +++ b/src/ui/zcl_abapgit_gui_page_view_repo.clas.abap @@ -15,6 +15,8 @@ CLASS zcl_abapgit_gui_page_view_repo DEFINITION toggle_changes TYPE string VALUE 'toggle_changes' ##NO_TEXT, toggle_diff_first TYPE string VALUE 'toggle_diff_first ' ##NO_TEXT, display_more TYPE string VALUE 'display_more' ##NO_TEXT, + repo_switch_origin_to_pr TYPE string VALUE 'repo_switch_origin_to_pr', + repo_reset_origin TYPE string VALUE 'repo_reset_origin', END OF c_actions. @@ -121,10 +123,23 @@ CLASS zcl_abapgit_gui_page_view_repo DEFINITION io_tb_tag TYPE REF TO zcl_abapgit_html_toolbar io_tb_advanced TYPE REF TO zcl_abapgit_html_toolbar RETURNING VALUE(ro_toolbar) TYPE REF TO zcl_abapgit_html_toolbar - RAISING zcx_abapgit_exception. + RAISING zcx_abapgit_exception, + switch_to_pr + IMPORTING + it_fields TYPE tihttpnvp OPTIONAL + iv_revert TYPE abap_bool OPTIONAL + RETURNING + VALUE(rv_switched) TYPE abap_bool + RAISING + zcx_abapgit_exception. METHODS build_main_menu RETURNING VALUE(ro_menu) TYPE REF TO zcl_abapgit_html_toolbar. + METHODS render_scripts + RETURNING + VALUE(ri_html) TYPE REF TO zif_abapgit_html + RAISING + zcx_abapgit_exception. ENDCLASS. @@ -263,6 +278,8 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_VIEW_REPO IMPLEMENTATION. METHOD build_branch_dropdown. + DATA lo_repo_online TYPE REF TO zcl_abapgit_repo_online. + CREATE OBJECT ro_branch_dropdown. IF mo_repo->is_offline( ) = abap_true. @@ -279,6 +296,17 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_VIEW_REPO IMPLEMENTATION. ro_branch_dropdown->add( iv_txt = 'Delete' iv_act = |{ zif_abapgit_definitions=>c_action-git_branch_delete }?{ mv_key }| ). + lo_repo_online ?= mo_repo. " TODO refactor this disaster + IF lo_repo_online->get_switched_origin( ) IS NOT INITIAL. + ro_branch_dropdown->add( + iv_txt = 'Switch Origin: Revert beta' + iv_act = |{ c_actions-repo_reset_origin }| ). + ELSE. + ro_branch_dropdown->add( + iv_txt = 'Switch Origin: to PR beta' + iv_act = |{ c_actions-repo_switch_origin_to_pr }| ). + ENDIF. + ENDMETHOD. @@ -524,7 +552,7 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_VIEW_REPO IMPLEMENTATION. lv_package = mo_repo->get_package( ). mv_are_changes_recorded_in_tr = zcl_abapgit_factory=>get_sap_package( lv_package - )->are_changes_recorded_in_tr_req( ). + )->are_changes_recorded_in_tr_req( ). ENDMETHOD. @@ -652,8 +680,6 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_VIEW_REPO IMPLEMENTATION. gui_services( )->get_hotkeys_ctl( )->register_hotkeys( me ). gui_services( )->register_event_handler( me ). - register_deferred_script( - zcl_abapgit_gui_chunk_lib=>render_repo_palette( zif_abapgit_definitions=>c_action-go_repo ) ). " Reinit, for the case of type change mo_repo = zcl_abapgit_repo_srv=>get_instance( )->get( mo_repo->get_key( ) ). @@ -782,6 +808,8 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_VIEW_REPO IMPLEMENTATION. ix_error = lx_error ) ). ENDTRY. + register_deferred_script( render_scripts( ) ). + ENDMETHOD. @@ -1032,9 +1060,59 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_VIEW_REPO IMPLEMENTATION. ENDMETHOD. + METHOD render_scripts. + + DATA lv_json TYPE string. + + CREATE OBJECT ri_html TYPE zcl_abapgit_html. + + ri_html->set_title( cl_abap_typedescr=>describe_by_object_ref( me )->get_relative_name( ) ). + ri_html->add( zcl_abapgit_gui_chunk_lib=>render_repo_palette( zif_abapgit_definitions=>c_action-go_repo ) ). + + ENDMETHOD. + + + METHOD switch_to_pr. + + DATA lo_repo_online TYPE REF TO zcl_abapgit_repo_online. + DATA ls_field LIKE LINE OF it_fields. + DATA lt_pulls TYPE zif_abapgit_pr_enum_provider=>tty_pulls. + DATA ls_pull LIKE LINE OF lt_pulls. + + IF mo_repo->is_offline( ) = abap_true. + zcx_abapgit_exception=>raise( 'Unexpected PR switch for offline repo' ). + ENDIF. + IF mo_repo->get_local_settings( )-write_protected = abap_true. + zcx_abapgit_exception=>raise( 'Cannot switch branch. Local code is write-protected by repo config' ). + ENDIF. + + lo_repo_online ?= mo_repo. + + IF iv_revert = abap_true. + lo_repo_online->switch_origin( '' ). + ELSE. + lt_pulls = zcl_abapgit_pr_enumerator=>new( lo_repo_online )->get_pulls( ). + IF lines( lt_pulls ) = 0. + RETURN. " false + ENDIF. + + ls_pull = zcl_abapgit_ui_factory=>get_popups( )->choose_pr_popup( lt_pulls ). + IF ls_pull IS INITIAL. + RETURN. " false + ENDIF. + + lo_repo_online->switch_origin( ls_pull-head_url ). + lo_repo_online->set_branch_name( |refs/heads/{ ls_pull-head_branch }| ). " TODO refactor + rv_switched = abap_true. + ENDIF. + + ENDMETHOD. + + METHOD zif_abapgit_gui_event_handler~on_event. - DATA: lv_path TYPE string. + DATA lv_path TYPE string. + DATA lv_switched TYPE abap_bool. CASE iv_action. WHEN zif_abapgit_definitions=>c_action-go_repo. " Switch to another repo @@ -1082,9 +1160,21 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_VIEW_REPO IMPLEMENTATION. open_in_master_language( ). ev_state = zcl_abapgit_gui=>c_event_state-re_render. + WHEN c_actions-repo_switch_origin_to_pr. + lv_switched = switch_to_pr( ). + IF lv_switched = abap_true. + ev_state = zcl_abapgit_gui=>c_event_state-re_render. + ELSE. + ev_state = zcl_abapgit_gui=>c_event_state-no_more_act. + ENDIF. + + WHEN c_actions-repo_reset_origin. + switch_to_pr( iv_revert = abap_true ). + ev_state = zcl_abapgit_gui=>c_event_state-re_render. + WHEN OTHERS. - super->zif_abapgit_gui_event_handler~on_event( + super->zif_abapgit_gui_event_handler~on_event( " TODO refactor, move to HOC components EXPORTING iv_action = iv_action iv_getdata = iv_getdata diff --git a/src/ui/zcl_abapgit_popups.clas.abap b/src/ui/zcl_abapgit_popups.clas.abap index 9de137543..3fe5ca5be 100644 --- a/src/ui/zcl_abapgit_popups.clas.abap +++ b/src/ui/zcl_abapgit_popups.clas.abap @@ -575,6 +575,51 @@ CLASS ZCL_ABAPGIT_POPUPS IMPLEMENTATION. ENDMETHOD. + METHOD zif_abapgit_popups~choose_pr_popup. + + DATA lv_answer TYPE c LENGTH 1. + DATA lt_selection TYPE TABLE OF spopli. + FIELD-SYMBOLS LIKE LINE OF lt_selection. + FIELD-SYMBOLS LIKE LINE OF it_pulls. + + IF lines( it_pulls ) = 0. + zcx_abapgit_exception=>raise( 'No pull requests to select from' ). + ENDIF. + + LOOP AT it_pulls ASSIGNING . + APPEND INITIAL LINE TO lt_selection ASSIGNING . + -varoption = |{ -number } - { -title } @{ -user }|. + ENDLOOP. + + CALL FUNCTION 'POPUP_TO_DECIDE_LIST' + EXPORTING + textline1 = 'Select pull request' + titel = 'Select pull request' + start_col = 30 + start_row = 5 + IMPORTING + answer = lv_answer + TABLES + t_spopli = lt_selection + EXCEPTIONS + OTHERS = 1. "#EC NOTEXT + IF sy-subrc <> 0. + zcx_abapgit_exception=>raise( 'Error from POPUP_TO_DECIDE_LIST' ). + ENDIF. + + IF lv_answer = c_answer_cancel. + RETURN. + ENDIF. + + READ TABLE lt_selection ASSIGNING WITH KEY selflag = abap_true. + ASSERT sy-subrc = 0. + + READ TABLE it_pulls INTO rs_pull INDEX sy-tabix. + ASSERT sy-subrc = 0. + + ENDMETHOD. + + METHOD zif_abapgit_popups~create_branch_popup. DATA: lt_fields TYPE TABLE OF sval. diff --git a/src/ui/zcl_abapgit_ui_injector.clas.testclasses.abap b/src/ui/zcl_abapgit_ui_injector.clas.testclasses.abap index fe7630c39..6e1ff75d8 100644 --- a/src/ui/zcl_abapgit_ui_injector.clas.testclasses.abap +++ b/src/ui/zcl_abapgit_ui_injector.clas.testclasses.abap @@ -100,6 +100,10 @@ CLASS ltcl_abapgit_popups_mock IMPLEMENTATION. ENDMETHOD. + METHOD zif_abapgit_popups~choose_pr_popup. + + ENDMETHOD. + ENDCLASS. CLASS ltcl_no_dependency_injection IMPLEMENTATION. diff --git a/src/ui/zif_abapgit_popups.intf.abap b/src/ui/zif_abapgit_popups.intf.abap index 503af82ad..49a9222d5 100644 --- a/src/ui/zif_abapgit_popups.intf.abap +++ b/src/ui/zif_abapgit_popups.intf.abap @@ -163,4 +163,11 @@ INTERFACE zif_abapgit_popups VALUE(rt_proxy_bypass) TYPE zif_abapgit_definitions=>ty_range_proxy_bypass_url RAISING zcx_abapgit_exception. + METHODS choose_pr_popup + IMPORTING + it_pulls TYPE zif_abapgit_pr_enum_provider=>tty_pulls + RETURNING + VALUE(rs_pull) TYPE zif_abapgit_pr_enum_provider=>ty_pull_request + RAISING + zcx_abapgit_exception. ENDINTERFACE. diff --git a/src/utils/zcl_abapgit_login_manager.clas.abap b/src/utils/zcl_abapgit_login_manager.clas.abap index 94ecf0806..d93708e39 100644 --- a/src/utils/zcl_abapgit_login_manager.clas.abap +++ b/src/utils/zcl_abapgit_login_manager.clas.abap @@ -29,6 +29,13 @@ CLASS zcl_abapgit_login_manager DEFINITION VALUE(rv_auth) TYPE string RAISING zcx_abapgit_exception . + CLASS-METHODS get + IMPORTING + !iv_uri TYPE string + RETURNING + VALUE(rv_auth) TYPE string + RAISING + zcx_abapgit_exception . PROTECTED SECTION. PRIVATE SECTION. @@ -76,6 +83,18 @@ CLASS ZCL_ABAPGIT_LOGIN_MANAGER IMPLEMENTATION. ENDMETHOD. + METHOD get. + + DATA ls_auth LIKE LINE OF gt_auth. + + READ TABLE gt_auth INTO ls_auth WITH KEY uri = zcl_abapgit_url=>host( iv_uri ). + IF sy-subrc = 0. + rv_auth = ls_auth-authorization. + ENDIF. + + ENDMETHOD. + + METHOD load. DATA: ls_auth LIKE LINE OF gt_auth. diff --git a/src/utils/zcl_abapgit_string_map.clas.abap b/src/utils/zcl_abapgit_string_map.clas.abap index c20617e12..b9e3a9b68 100644 --- a/src/utils/zcl_abapgit_string_map.clas.abap +++ b/src/utils/zcl_abapgit_string_map.clas.abap @@ -57,9 +57,10 @@ CLASS zcl_abapgit_string_map DEFINITION zcx_abapgit_exception. METHODS freeze. + DATA mt_entries TYPE tts_entries READ-ONLY. + PROTECTED SECTION. PRIVATE SECTION. - DATA mt_entries TYPE tts_entries. DATA mv_read_only TYPE abap_bool. ENDCLASS. diff --git a/src/zcl_abapgit_factory.clas.abap b/src/zcl_abapgit_factory.clas.abap index 5b567f0b2..83b752ab2 100644 --- a/src/zcl_abapgit_factory.clas.abap +++ b/src/zcl_abapgit_factory.clas.abap @@ -39,6 +39,9 @@ CLASS zcl_abapgit_factory DEFINITION CLASS-METHODS get_longtexts RETURNING VALUE(ri_longtexts) TYPE REF TO zif_abapgit_longtexts . + CLASS-METHODS get_http_agent + RETURNING + VALUE(ri_http_agent) TYPE REF TO zif_abapgit_http_agent . PROTECTED SECTION. PRIVATE SECTION. @@ -74,6 +77,7 @@ CLASS zcl_abapgit_factory DEFINITION CLASS-DATA gi_cts_api TYPE REF TO zif_abapgit_cts_api . CLASS-DATA gi_environment TYPE REF TO zif_abapgit_environment . CLASS-DATA gi_longtext TYPE REF TO zif_abapgit_longtexts . + CLASS-DATA gi_http_agent TYPE REF TO zif_abapgit_http_agent . ENDCLASS. @@ -133,6 +137,17 @@ CLASS ZCL_ABAPGIT_FACTORY IMPLEMENTATION. ENDMETHOD. + METHOD get_http_agent. + + IF gi_http_agent IS BOUND. + ri_http_agent = gi_http_agent. + ELSE. + ri_http_agent = zcl_abapgit_http_agent=>create( ). + ENDIF. + + ENDMETHOD. + + METHOD get_longtexts. IF gi_longtext IS NOT BOUND. diff --git a/src/zcl_abapgit_injector.clas.abap b/src/zcl_abapgit_injector.clas.abap index 4afc4c28b..608204028 100644 --- a/src/zcl_abapgit_injector.clas.abap +++ b/src/zcl_abapgit_injector.clas.abap @@ -27,6 +27,9 @@ CLASS zcl_abapgit_injector DEFINITION CLASS-METHODS set_longtexts IMPORTING !ii_longtexts TYPE REF TO zif_abapgit_longtexts . + CLASS-METHODS set_http_agent + IMPORTING + !ii_http_agent TYPE REF TO zif_abapgit_http_agent . PROTECTED SECTION. PRIVATE SECTION. ENDCLASS. @@ -69,6 +72,11 @@ CLASS ZCL_ABAPGIT_INJECTOR IMPLEMENTATION. ENDMETHOD. + METHOD set_http_agent. + zcl_abapgit_factory=>gi_http_agent = ii_http_agent. + ENDMETHOD. + + METHOD set_longtexts. zcl_abapgit_factory=>gi_longtext = ii_longtexts. ENDMETHOD. diff --git a/src/zcl_abapgit_repo.clas.abap b/src/zcl_abapgit_repo.clas.abap index 7a520feea..9ad7fa4cb 100644 --- a/src/zcl_abapgit_repo.clas.abap +++ b/src/zcl_abapgit_repo.clas.abap @@ -173,6 +173,7 @@ CLASS zcl_abapgit_repo DEFINITION !is_local_settings TYPE zif_abapgit_persistence=>ty_repo-local_settings OPTIONAL !iv_deserialized_at TYPE zif_abapgit_persistence=>ty_repo-deserialized_at OPTIONAL !iv_deserialized_by TYPE zif_abapgit_persistence=>ty_repo-deserialized_by OPTIONAL + !iv_switched_origin TYPE zif_abapgit_persistence=>ty_repo-switched_origin OPTIONAL RAISING zcx_abapgit_exception . METHODS reset_remote . @@ -658,7 +659,7 @@ CLASS ZCL_ABAPGIT_REPO IMPLEMENTATION. METHOD set. -* TODO: refactor +* TODO: refactor, maybe use zcl_abapgit_string_map ? DATA: ls_mask TYPE zif_abapgit_persistence=>ty_repo_meta_mask. @@ -671,7 +672,8 @@ CLASS ZCL_ABAPGIT_REPO IMPLEMENTATION. OR is_dot_abapgit IS SUPPLIED OR is_local_settings IS SUPPLIED OR iv_deserialized_by IS SUPPLIED - OR iv_deserialized_at IS SUPPLIED. + OR iv_deserialized_at IS SUPPLIED + OR iv_switched_origin IS SUPPLIED. IF it_checksums IS SUPPLIED. @@ -716,6 +718,11 @@ CLASS ZCL_ABAPGIT_REPO IMPLEMENTATION. ls_mask-deserialized_by = abap_true. ENDIF. + IF iv_switched_origin IS SUPPLIED. + ms_data-switched_origin = iv_switched_origin. + ls_mask-switched_origin = abap_true. + ENDIF. + notify_listener( ls_mask ). ENDMETHOD. diff --git a/src/zcl_abapgit_repo_online.clas.abap b/src/zcl_abapgit_repo_online.clas.abap index 85fc9f03e..b33abc011 100644 --- a/src/zcl_abapgit_repo_online.clas.abap +++ b/src/zcl_abapgit_repo_online.clas.abap @@ -46,6 +46,16 @@ CLASS zcl_abapgit_repo_online DEFINITION VALUE(rv_url) TYPE zif_abapgit_persistence=>ty_repo-url RAISING zcx_abapgit_exception . + METHODS get_switched_origin + RETURNING + VALUE(rv_url) TYPE zif_abapgit_persistence=>ty_repo-switched_origin . + METHODS switch_origin + IMPORTING + !iv_url TYPE zif_abapgit_persistence=>ty_repo-url + !iv_overwrite TYPE abap_bool DEFAULT abap_false + RAISING + zcx_abapgit_exception . + METHODS get_files_remote REDEFINITION . @@ -169,6 +179,11 @@ CLASS ZCL_ABAPGIT_REPO_ONLINE IMPLEMENTATION. ENDMETHOD. + METHOD get_switched_origin. + rv_url = ms_data-switched_origin. + ENDMETHOD. + + METHOD get_url. rv_url = ms_data-url. ENDMETHOD. @@ -318,6 +333,44 @@ CLASS ZCL_ABAPGIT_REPO_ONLINE IMPLEMENTATION. ENDMETHOD. + METHOD switch_origin. + + DATA lv_offs TYPE i. + + IF iv_overwrite = abap_true. " For repo settings page + set( iv_switched_origin = iv_url ). + RETURN. + ENDIF. + + IF iv_url IS INITIAL. + IF ms_data-switched_origin IS INITIAL. + RETURN. + ELSE. + lv_offs = find( + val = reverse( ms_data-switched_origin ) + sub = '@' ). + IF lv_offs = -1. + zcx_abapgit_exception=>raise( 'Incorrect format of switched origin' ). + ENDIF. + lv_offs = strlen( ms_data-switched_origin ) - lv_offs - 1. + set_url( substring( + val = ms_data-switched_origin + len = lv_offs ) ). + set_branch_name( substring( + val = ms_data-switched_origin + off = lv_offs + 1 ) ). + set( iv_switched_origin = '' ). + ENDIF. + ELSEIF ms_data-switched_origin IS INITIAL. + set( iv_switched_origin = ms_data-url && '@' && ms_data-branch_name ). + set_url( iv_url ). + ELSE. + zcx_abapgit_exception=>raise( 'Cannot switch origin twice' ). + ENDIF. + + ENDMETHOD. + + METHOD zif_abapgit_git_operations~create_branch. DATA: lv_sha1 TYPE zif_abapgit_definitions=>ty_sha1.